Sensitive Data in Logs
debt(d7/e3/b3/t7)
Closest to 'only careful code review or runtime testing' (d7). Semgrep and trufflehog can catch obvious patterns, but most leaks happen via dynamic context arrays (print_r($_POST), exception messages) that static tools miss until logs are reviewed.
Closest to 'simple parameterised fix' (e3). Quick_fix is adding a Monolog processor that redacts sensitive keys centrally — one component change, but more than a one-line patch since you need to enumerate keys and wire the processor.
Closest to 'localised tax' (b3). Once a scrubbing processor is in place, logging discipline becomes a modest ongoing tax — devs must remember not to log raw request data, but it doesn't shape architecture.
Closest to 'serious trap' (t7). The misconception that logs are dev-only contradicts reality (SIEM, third-party aggregators, support access); the 'obvious' debug pattern (print_r($_POST)) is exactly the wrong thing to do.
Also Known As
TL;DR
Explanation
Application logs are collected by aggregators (ELK, Datadog, Splunk) where they are stored, indexed, and accessed by many more people than the primary database. Logging request parameters, exception messages containing credentials, or full user objects routinely exposes sensitive data. Structured logging makes this worse — a well-intentioned context object dumps everything. Always explicitly allowlist what gets logged rather than logging everything and filtering after.
Common Misconception
Why It Matters
Common Mistakes
- Logging entire request arrays: error_log(print_r($_POST, true)) — captures passwords and tokens.
- Logging exception messages that include SQL with bound parameters containing user data.
- Logging full user objects including hashed passwords and API keys.
- Not masking card numbers — only log last 4 digits: ****1234.
Code Examples
// Logs entire POST including passwords:
error_log('Request: ' . print_r($_POST, true));
// Logs exception with credentials in message:
try {
authenticate($user, $password);
} catch (Exception $e) {
$logger->error($e->getMessage()); // May contain password
$logger->debug('Context', ['user' => $user, 'pass' => $password]);
}
// Explicit allowlist — only log safe fields:
$logger->info('Login attempt', [
'user_id' => $user->id,
'ip' => $request->ip(),
'success' => $authenticated,
// No password, no token, no PII beyond user_id
]);
// Mask sensitive values:
$logger->debug('Payment', [
'card_last4' => substr($card, -4),
'amount' => $amount,
// Never: full card number, CVV, or auth token
]);