Skip to content

CWE-778: Insufficient Logging

Overview

Insufficient logging occurs when a security-critical event - a login attempt, an authorization failure, a privilege change, a sensitive data access - either isn't recorded at all, or is recorded without enough detail to reconstruct what happened. Monitoring, incident response, and audit all read from those records, so the gap stays invisible until the moment someone needs them.

Relationship to Other CWEs

CWE-778 is a child of CWE-223 (Omission of Security-relevant Information) - the general case of a product failing to record or display information needed to identify an attack or judge whether an action was safe. CWE-778 is its most common instance: the missing information is a security event log entry. If a finding is about security-relevant information omitted from something other than event logging - a warning, an indicator, an audit report - use CWE-223 instead.

Logging fixes routinely write attacker-controlled values - a submitted username, a requested path, a user agent - into the log. Neutralize newlines and control characters in those values before they reach the log, or a forged entry becomes the next problem: see CWE-117 (Log Injection).

OWASP Classification

A09:2025 - Security Logging & Alerting Failures

Risk

Medium: An attack on a system that records no security events leaves no trace, so it is noticed late or not at all, and the investigation afterwards has nothing to reconstruct from. Compliance regimes that mandate an audit trail (PCI DSS, HIPAA, SOX) treat the gap as a finding in its own right.

Remediation Steps

Core Principle: Record every security-relevant event with enough context to reconstruct what happened, who did it, and whether it was authorized; fail loudly if that recording itself fails.

Identify What's Missing

Walk the paths where the application makes a security decision and check whether each one writes anything:

  • Authentication flows: login, logout, password reset, MFA, session creation and destruction
  • Authorization points: access-denied events, role and permission checks, resource access attempts
  • Sensitive operations: data create/update/delete, admin actions, configuration changes
  • Any minimum event set a compliance regime already imposes (PCI DSS, HIPAA, SOX, GDPR)

Log Every Security-Relevant Event (Primary Defense)

At minimum, record:

  • Authentication: Login (success and failure), logout, password change, MFA enable/disable, session creation/expiration
  • Authorization: Access granted/denied, permission changes, role assignments
  • Data access: View/modify sensitive data, exports, searches
  • Administrative: Config changes, user creation/deletion, privilege escalation
  • Security events: Account lockouts, suspicious patterns, policy violations

Log the success case as well as the failure case for each of these.

Include Sufficient Context

An entry has to answer the questions an investigator will ask of it. Each event should carry:

  • When: Timestamp (UTC), ideally with a consistent, sortable format
  • What: Event type, action, and result (success/failure/denied)
  • Who: User identifier, session ID
  • Where: Source IP address, user agent, endpoint, method
  • Context: A correlation/request ID, plus event-specific details (which resource, what permission was missing, why the request failed)

Use Structured, Machine-Parseable Logging

Emit events as structured data - JSON or an equivalent key-value format - rather than free-text messages. Aggregation and SIEM tooling can then parse entries reliably, correlate them across services on a shared request ID, and query the specific field an investigation needs. None of that is reliable against messages assembled by ad hoc string interpolation.

Protect Log Integrity and Enable Monitoring

Recording the event is not the end of the job:

  • Restrict log file/stream permissions so only the logging pipeline and authorized operators can write or read them
  • Use append-only storage or a hash-chained, tamper-evident format for logs that may need to stand up as evidence
  • Alert on the patterns that matter operationally: repeated failed logins, unexpected privilege grants, bulk data access
  • Surface a failed security log write as an operational event of its own

Test Logging Coverage

  • Verify each security-relevant code path (login, logout, permission checks, sensitive data access, admin actions) produces a log entry for both success and failure
  • Confirm each entry contains the required fields (timestamp, event type, action, result, user, source)
  • Confirm logs are shipped to durable, access-controlled storage and are visible to monitoring/alerting
  • Re-scan with the security scanner to confirm the finding is resolved

Common Vulnerable Patterns

// VULNERABLE - pseudo-code
function login(username, password):
    user = authenticate(username, password)
    if user:
        return create_token(user)          // no record that this login happened
    return error(401, "Invalid credentials")  // no record of the failed attempt either

function check_access(user, resource):
    if not has_permission(user, resource):
        return error(403, "Forbidden")      // denial happened, but nothing recorded who was denied what

function delete_user(id):
    db.delete(User, id)                     // no audit trail: who deleted what, when

Why this is vulnerable: None of these events - a login, an access denial, a data deletion - leaves any trace. There is nothing to reveal a brute-force attempt, nothing to show who read or deleted a record, and nothing to hand an incident responder or auditor afterwards.

Secure Patterns

// SECURE - pseudo-code
function log_security_event(event_type, action, result, user, details):
    write_structured_log({
        timestamp: now_utc(),
        event_type: event_type,       // authentication, authorization, data_modification, ...
        action: action,                // login, delete_user, access_sensitive_data, ...
        result: result,                // attempt, success, failure, denied
        user: user or "anonymous",
        ip_address: request.ip,
        request_id: current_request_id(),
        details: details
    })

function login(username, password):
    user = authenticate(username, password)
    if user:
        log_security_event("authentication", "login", "success", username, {})
        return create_token(user)
    log_security_event("authentication", "login", "failure", username, {reason: "invalid_credentials"})
    return error(401, "Invalid credentials")

function check_access(user, resource):
    if not has_permission(user, resource):
        log_security_event("authorization", "access_resource", "denied", user.id,
            {resource: resource.id, reason: "insufficient_permissions"})
        return error(403, "Forbidden")
    log_security_event("authorization", "access_resource", "success", user.id, {resource: resource.id})
    return resource

function delete_user(id, current_user):
    log_security_event("data_modification", "delete_user", "attempt", current_user.id, {target_user_id: id})
    try:
        db.delete(User, id)
    catch error:
        log_security_event("data_modification", "delete_user", "failure", current_user.id,
            {target_user_id: id, reason: error.message})
        raise
    log_security_event("data_modification", "delete_user", "success", current_user.id, {target_user_id: id})

Why this works: Every security-relevant branch, success and failure alike, produces a structured entry with who, what, when, where, and enough event-specific detail to investigate later. Authorization denials are logged before the error returns, so repeated denials show up as the reconnaissance or privilege-escalation attempt they are. Data-modifying actions carry an audit trail tying the change back to the acting user, and the outcome is recorded once the database call has returned - a delete that throws is written down as a failed attempt rather than as a deletion that never happened. The consistent structure means a SIEM or log analysis tool can query all of it without additional parsing work.

Common Pitfalls

  • Logging only failures, not successes: if only failed logins are recorded, there's no baseline to notice an unusual successful login (new location, off-hours, impossible travel) - log both outcomes for the same event type.
  • Logging the event without enough context to act on it: "access denied" with no user, resource, or reason recorded is only marginally better than no log at all - forensics needs who, what, when, and why, not just that something happened.
  • Treating log writes as fire-and-forget: if the logging call has no error handling around it, a full disk or an unreachable log service silently drops security events with no alert - a failed security log write should itself raise an operational alarm.
  • Shipping logs to a SIEM but never alerting on them: centralizing logs satisfies a retention requirement, not a detection one - without correlation rules and alert thresholds, the information gap just moves from "not recorded" to "recorded but never reviewed."

Additional Resources