Log scrubber
Source: src/security/log-scrubber.ts
Defence-in-depth layer for the application logger.
What Pino already does
Pino’s built-in redact handles known paths:
redact: { paths: ['req.headers["x-api-key"]', 'req.headers["x-admin-token"]'] }That covers the request-headers case completely.
What this scrubber adds
The harder case is arbitrary error objects that an adapter SDK might construct with token-shaped strings or access_token properties baked deep inside. Pino can’t redact a path it doesn’t know about. So we wire the scrubber as the err serializer in src/index.ts:
serializers: { err: (e) => scrubTokens({ type, message, stack }) }scrubTokens walks the value (depth-limited at 6) and:
- replaces
Bearer <anything>withBearer [REDACTED] - replaces
ya29.<chars>(Google OAuth) with[REDACTED] - replaces
EAA<chars>(Meta long-lived tokens) with[REDACTED] - replaces values of keys whose name matches
/access_token|refresh_token|client_secret|authorization|api[_-]?key|x-admin-token/i
When the regex matters most
Adapter SDKs sometimes embed access tokens directly into error messages — a token-prefixed string then ends up in err.message. Without this layer the token leaves the process via journalctl. Phase 5’s grep evidence check (looking for ya29. in the journal) becomes a regression detector, not the primary defence.
Tests
tests/integration/log-scrubber.test.ts covers:
Bearer <token>redactionya29.*,EAA*standalone redaction- key-name redaction (
access_token,refresh_token, etc.) - nested objects + arrays