# No PII, card data, or session tokens in server logs

- **Pattern:** `ab-002623` (`project-snapshot.abuse.no-pii-in-server-logs`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-23
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002623
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002623)

## Why it matters

Server-side `console.log(req.body)`, `logger.info({ user, session })`, or `Sentry.captureException(err, { extra: { headers } })` calls that dump full request bodies, card numbers, CVVs, session tokens, or API keys have two compounding cost consequences beyond privacy exposure. First, PCI scope expansion — the moment a credit-card field lands in a log line, the log ingestion pipeline (Datadog, CloudWatch, Loki, Sentry, Logtail) becomes "in-scope" for PCI-DSS audits, adding roughly $50K-$200K per year in compliance overhead for QSA assessments, retention encryption, and access-control review. Second, abuse-replay blast radius — anyone with log read access (your ops team, Sentry seat-holders, a compromised vendor) can replay sessions, hijack tokens, or reuse cards. The 2022 Twilio incident exposed years of customer PII because log infrastructure was compromised; Capital One's 2019 IAM-misconfigured S3 logs resulted in 100M records leaked. PCI-DSS Req 3.2 explicitly prohibits storing authentication data post-authorization in any unprotected location, including logs. AI scaffolding produces this pattern casually: "add logging" becomes `console.log(req.body)` without a redactor in the loop.

## Severity rationale

High because one tainted log pipeline can expand PCI scope by hundreds of thousands of dollars per year and any log reader effectively becomes an auth principal.

## Remediation

Strip sensitive fields before logging: introduce a `redact(obj)` helper that drops `password`, `token`, `apiKey`, `cardNumber`, `cvv`, `authorization`, `cookie`, `session`, and full `req.body` / `req.headers` objects, and route every `console.log` or `logger.*` call through it. For Sentry, configure `beforeSend` to scrub the same fields. Log scope markers (`[auth-flow] step 2 userId=abc123`) instead of full objects.

Deeper remediation guidance and cross-reference coverage for this check lives in the `security-hardening` and `saas-logging` Pro audits — run those after applying this fix for a more exhaustive pass on the same topic.

## Detection

- **ID:** `no-pii-in-server-logs`
- **Severity:** `high`
- **What to look for:** Grep server-side code directories (`app/api/**`, `pages/api/**`, server components, `lib/**`, `server/**`, middleware files) for `console.log`, `console.error`, `console.warn`, `console.debug`, `logger.info`, `logger.debug`, `logger.error`, `logger.warn`, and `Sentry.captureException(..., { extra: ... })` calls whose argument list includes any of: `req.body`, `req.headers`, `cookies()`, `headers()`, `user` (full object), `session`, `email`, `password`, `pinHash`, `token`, `apiKey`, `api_key`, `cardNumber`, `card_number`, `cvv`, `expiryDate`, `ssn`, `phone`, `address`, `firstName`, `lastName`. Also flag whole-request dumps: `console.log(req)`, `console.log(JSON.stringify(req.body))`, `logger.info(event)` where `event` is the raw handler input. Scope markers like `console.log('[auth-flow] step 2')` or `logger.info({ userId })` (opaque id only, no PII) are fine.
- **Pass criteria:** Zero server-side log or Sentry calls dumping PII, credit-card fields, session tokens, or full request bodies/headers.
- **Fail criteria:** One or more server-side log or Sentry calls dumping any of the listed sensitive fields. Detail must name the file and the log call type.
- **Skip (N/A) when:** No server-side code exists (project is a pure static SPA, CLI tool with no network handlers, or library with no runtime request-handling). Report the absence of `app/api`, `pages/api`, and `server/` directories as evidence.
- **Do NOT pass when:** A log call dumps `req.body` "for debugging" — it still ships to production log infrastructure.
- **Before evaluating, quote:** Quote the file path and the log line for every match. If zero matches, report `"0 sensitive-log matches across N server-side files scanned"`.
- **Report even on pass:** Report the count of server-side files scanned and the count of matches: `"Scanned ~X server-side files, 0 sensitive-field log patterns matched"`.
- **Detail on fail:** `"Full request body logged in app/api/auth/login/route.ts — console.log(req.body) includes password field"` — name the file, the log call, and the specific sensitive field being dumped.
- **Remediation:** Strip sensitive fields before logging: introduce a `redact(obj)` helper that drops `password`, `token`, `apiKey`, `cardNumber`, `cvv`, `authorization`, `cookie`, `session`, and full `req.body` / `req.headers` objects, and route every `console.log` or `logger.*` call through it. For Sentry, configure `beforeSend` to scrub the same fields. Log scope markers (`[auth-flow] step 2 userId=abc123`) instead of full objects.

## External references

- cwe CWE-532
- pci-dss Req 3.2

Taxons: project-snapshot, abuse-resistance, privacy-consent

HTML version: https://auditbuffet.com/patterns/ab-002623
