Secure Logging: Preventing Sensitive Data Leakage
A useful security log explains a material event without becoming a second copy of sensitive data. CWE-532 covers insertion of sensitive information into log files. OWASP says logs should record sufficient context for monitoring and investigation, but normally exclude passwords, access tokens, session identifiers, connection strings, encryption keys, payment data, and sensitive personal data. NIST SP 800-92 provides enterprise log-management guidance; it is not proof that any individual deployment is compliant.
Design event contract before logger call
Define event name, allowed fields, classification, retention, and consumer. Keep request body, authorization header, cookies, raw exception, and whole user object outside contract. Record outcome rather than payload. An interaction ID can correlate events without copying user input. OWASP recommends recording when, where, who, and what, while treating data from other trust zones as untrusted and sanitizing it before output.
| Event | Allowed evidence fields | Owner | Pass / fail |
|---|---|---|---|
auth.failure | timestamp, request ID, route, outcome, rate-limit bucket | Identity owner | Pass: no password, token, or raw username |
payment.declined | request ID, provider class, outcome | Payments owner | Fail: card payload or provider response body present |
dependency.error | request ID, dependency name, timeout class | Service owner | Fail: connection string or stack trace in shared log |
admin.role_change | actor ID, target ID, action, outcome | Access-control owner | Pass: audit trail is searchable and access-restricted |
Completed operational artifact: a versioned event-field register linked to code review and retention setting. It contains field names, allowed values, data class, owners, example redacted output, and last review date. Never use live secrets in examples.
Centralize redaction
Do not depend on every developer remembering what to hide. Build structured events, allowlist fields, and sanitize untrusted values before serialization. This TypeScript example strips line breaks that could forge records and removes named sensitive fields:
const blocked = new Set(["authorization", "cookie", "password", "token"]);
function audit(event: string, fields: Record<string, unknown>) {
const safe = Object.fromEntries(Object.entries(fields)
.filter(([key]) => !blocked.has(key.toLowerCase()))
.map(([key, value]) => [key, String(value).replace(/[\r\n]/g, " ").slice(0, 160)]));
console.log(JSON.stringify({ event, ...safe }));
}
audit("dependency.error", { requestId, dependency: "mail", errorClass: "timeout" });
This is an example, not universal redaction. Nested objects, URLs containing credentials, SDK error objects, and framework request dumps need dedicated review. Prefer typed event builders over console.log(error) or logger.info({ request }). Preserve error cause only in protected diagnostic storage if operational need and access controls justify it.
Test both content and failure mode
Unit tests should inject synthetic marker strings such as TEST_SECRET_NEVER_LOG into headers, cookies, request body, and thrown errors. Test output must not contain marker. Fuzz newline and delimiter characters to confirm one user value cannot create another log entry. Simulate collector outage, full disk, denied write permission, and serialization failure. OWASP calls for verifying log injection resistance, access control, resource exhaustion, and application behavior when logging fails. Logging failure must not expose details or turn normal service traffic into a denial of service.
Access, retention, and exception flow
Separate log-reader, log-writer, and retention-deletion privileges. Send logs over authenticated encrypted transport when crossing trust boundaries. Protect stored records from unauthorized modification or deletion, and monitor access. Retention must be stated per event class; keep data no longer than investigation need and approved policy allow. Review debug sinks, developer consoles, backups, and exported incident files because they can bypass primary retention.
When a team needs temporary extra detail, owner files an exception containing event name, exact extra field, purpose, risk, storage location, reader group, expiry, compensating control, and remediation ticket. Example: EXC-LOG-007 permits a pseudonymized upstream request ID for seven days during a provider incident. It does not permit raw headers or credential values. On expiry, configuration reverts and owner attaches deletion or expiry evidence.
Acceptance passes when event register exists, synthetic secret scan is clean, logs resist line injection, readers are restricted, retention is configured, and failure tests preserve safe application behavior. Failure means stop broader access, remove offending fields, assess existing log exposure, rotate any logged credential, and rerun evidence tests. Source review can trace logging wrappers and high-risk call sites.
Capture closure evidence
Execute redaction test with unique synthetic marker in header, cookie, nested object, URL, and exception; search collector, backup export, and console sink. Expected output contains event and request ID but no marker or injected newline record. Attach search command, time range, sink names, retention class, owner, and pass/fail result. If marker appears, stop export access, remove field at builder boundary, assess retained copies, and rerun after deletion evidence.
Worked event review and closure
Take one failed sign-in request and follow it through edge, application, queue, and observability sink. Safe event fields are timestamp, request correlation ID, route class, outcome, and bounded error code. Unsafe fields include password, session cookie, authorization header, reset token, raw IP address, and entire request body. In staging, submit synthetic marker log-marker-7f3c only in field that must be redacted, then query each log sink. Expected result: event exists for traceability, marker has no match, and correlation ID links records without revealing person or credential.
An edge case is structured logging that redacts string messages but leaves nested object values unchanged. Test JSON serialization, exception middleware, tracing attributes, and third-party SDK breadcrumbs separately. Another edge case is a support export created from retained logs; export policy must preserve same redaction and access restrictions.
Close review with field allowlist, marker-search output, retention setting, role-access evidence, and owner of every sink. Reopen when new middleware, telemetry vendor, exception handler, or data-bearing route is introduced. Logging proves operation; it must not become an alternate database of sensitive input.