Lorem, ipsum dolor sit amet consectetur adipisicing elit. Qui, itaque voluptate ipsa non enim amet ducimus voluptatibus deserunt nam esse!
Auditing Log Coverage: Finding the Events You Are Not Recording

Auditing Log Coverage: Finding the Events You Are Not Recording

pr0h0
loggingobservabilityauditmonitoring
AI Usage (85%)

Introduction

Logging is not the same as having the right logs. Most applications write something—error stacks, request traces, maybe a few console.log calls that survived code review. But when a security incident lands on your desk, the question is never “do you have logs.” It is “do you have the log that shows what actually happened.”

Missing events are invisible until you need them. I have walked into too many codebases that log every incoming request but silently swallow failed authentication, deny decisions, or privilege escalations. That gap turns a one‑hour investigation into a three‑day guessing game.

What Does “Log Coverage” Mean?

Log coverage is the set of security‑relevant events your application actually records. It is not about log volume. It is about whether critical signals—failed logins, access‑denied responses, permission changes, sensitive data reads—reach your log pipeline at all.

A decent monitoring setup can still have zero coverage for authorization abuse if the only thing you log is GET /dashboard 200.

The Systematic Audit Approach

You cannot fix coverage by staring at dashboards. You need a structured walk through the application.

Define Your Event Inventory

Start with a short, concrete list of events a defender or incident responder would kill to have. Not everything—focus on high‑signal security events:

  • Successful and failed authentication
  • Account lockout / brute‑force signals
  • MFA enrollment or bypass
  • Privilege escalation or role changes
  • Access to sensitive resources (admin panels, billing, user PII)
  • Dangerous configuration changes (CORS, CSP, feature flags)
  • Data export or bulk download

Write these down in a spreadsheet. This is your target.

Map Code Paths to Events

Now pick each event and trace the real code paths that trigger it. Do not assume a path is logged because one branch is. I usually grep for log, logger, or console near authentication middleware, authorization checks, and API handlers.

// routes/auth.js – looks complete, right?
async function login(req, res) {
  const user = await findUser(req.body.email);
  if (!user) {
    // ❌ no log here – attacker probes valid emails silently
    return res.status(401).json({ error: 'Invalid credentials' });
  }
  const valid = await bcrypt.compare(req.body.password, user.hash);
  if (!valid) {
    audit.log('LOGIN_FAILED', { email: req.body.email }); // ✅ logged
    return res.status(401).json({ error: 'Invalid credentials' });
  }
  // ... session creation, logs success
}
💪

Don't rely on code review alone. Static analysis with pattern matching (grep for 401, 403, audit) catches many gaps that eyes skip.

Collect Evidence

For each event in your inventory, record one real log entry—or a clear absent location. Evidence can be a sample from a test environment, a code snippet, or a test that proves the log fires. If you cannot produce the log, you have a gap.

Practical Walkthrough: Auditing a Login Flow

Let's ground this in a concrete example.

Expected Log Events for Login

A login flow should produce at minimum:

  • LOGIN_SUCCESS – with user ID, IP, maybe user agent
  • LOGIN_FAILED – with username/email, failure reason (bad password, no user, locked) -ACCOUNT_LOCKED – when a threshold is hit -MFA_CHALLENGE_ISSUED and MFA_FAILED if applicable

Checking the Actual Code/Logs

I reviewed a Node.js/Express backend that had a single audit.info call inside the catch‑all error handler. The login route itself had no logging for the case where the user does not exist—only a generic 401 response. In production logs, I could see requests but could not distinguish between “invalid email” and “wrong password” without additional tooling.

Gaps Found

The gaps:

  • No log for unknown user – allowed user enumeration detection to go blind
  • No differentiation of failure types – password vs. locked vs. MFA timeout all looked like a simple 401
  • No log for successful login after password reset – an important signal for account takeover

Those three gaps removed the ability to spot credential‑stuffing patterns and made incident triage rely on network‑level heuristics.

Analyzing and Prioritizing Gaps

Not every missing log is an emergency. I rank gaps by:

  • Attacker value: can an attacker use the lack of visibility to hide? (e.g., unlogged privilege escalation)
  • Incident response need: would a responder immediately ask for this event?
  • Regulatory / compliance weight: PCI, SOC2, and others require specific audit trails.

Authentication failures nearly always land at the top. Unlogged access‑denied decisions are a close second.

Closing the Gaps

Add the missing log calls directly into the code. Keep the format consistent:

  • structured JSON
  • mandatory fields: timestamp, event type, actor, outcome
  • correlation ID for tracing the session

Write a quick integration test that asserts the log appears for every significant branch. This prevents regressions when someone refactors the login flow later.

📝

One test per log event is lightweight and pays off quickly. I drop these into the same test suite that already exercises authentication.

Further Reading

Share this post

More posts

Comments