Skip to content

fix: redact credential headers from the request log - #1747

Merged
TheLastCicada merged 1 commit into
v2-rc2from
fix/redact-api-key-in-request-log
Aug 12, 2026
Merged

fix: redact credential headers from the request log#1747
TheLastCicada merged 1 commit into
v2-rc2from
fix/redact-api-key-in-request-log

Conversation

@TheLastCicada

@TheLastCicada TheLastCicada commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Problem

The request logger middleware attached the entire inbound header map to its log record:

logger.verbose(`Received request: ${req.method} ${req.originalUrl}`, {
  method: req.method,
  url: req.originalUrl,
  headers: req.headers,   // includes x-api-key
  timestamp: new Date().toISOString(),
});

Every authenticated request therefore wrote the plaintext x-api-key to
application-%DATE%.log and, in production, to stdout — which is what log shippers
forward to aggregators.

APP.LOG_LEVEL did not suppress this. In winston 3 a transport's own level overrides
the logger's level rather than being capped by it, and both relevant transports in
src/config/logger.js are pinned to level: 'debug', so the verbose record was
emitted even at LOG_LEVEL: info. Verified against the installed winston 3.19.0: a
transport at debug receives verbose records while the logger level is info.

Change

  • New src/utils/log-redaction.js with a redactHeaders helper that returns a copy
    with sensitive values replaced by [REDACTED]. The header name is preserved, so
    logs still show that a credential was supplied.
  • src/middleware.js passes redactHeaders(req.headers) — this closes the leak.
  • src/config/logger.js adds the same redaction as a winston format at the front of
    the shared chain, so a future record elsewhere that carries a header map is scrubbed
    too. It handles both info.headers and info.metadata.headers so reordering the
    chain later cannot silently reopen the hole.

Scope note for reviewers

The redaction set is deliberately wider than x-api-key: authorization,
proxy-authorization, cookie and set-cookie are also redacted. Since the request
logger logs the full header map on every request, this means those values no longer
appear in verbose request logs or in the log artifacts CI uploads. That is an
intentional trade-off — flagging it in case anyone relies on those fields for auth
debugging.

Redaction covers the top-level headers field only; a header map nested deeper in a
log record still reaches the log stream. The limitation is documented in the code.

Operational follow-up (not addressed by this PR)

Deployments whose logs were already shipped should rotate CADT_API_KEY and purge the
existing application-*.log files, including the gzipped archives, since the rotated
logs retain 30 days of history.

Test plan

  • New tests/integration/log-redaction.spec.js: 10 tests covering the helper, the
    winston format at every log level, non-mutation of caller-supplied objects, and an
    end-to-end request through the app asserting the key never reaches the output.
  • Confirmed non-vacuous: reverting the middleware line fails
    hands the logger a record that is already redacted; reverting the format fails
    the logger-level tests; reverting the metadata copy fails the non-mutation test.
  • npm run test:v1 — 163 passing, 5 pending
  • v2 integration suite — 1751 passing
  • eslint and prettier --check clean on all touched files. Note src/middleware.js
    has pre-existing no-unused-vars errors and Prettier drift on the base branch;
    both reproduce on the unmodified file and were left alone to keep this diff small.

Note

Low Risk
Defensive logging change that reduces credential exposure; no auth or request-handling logic changes beyond what gets logged.

Overview
Stops plaintext API keys and other credentials from being written to application logs and production stdout on every authenticated request.

The request logger previously attached the full req.headers map to verbose records; with transports pinned to debug, those records still shipped even when APP.LOG_LEVEL was info. This PR adds redactHeaders in log-redaction.js (sensitive names include x-api-key, authorization, cookies) and uses it in middleware at the call site. A Winston format at the start of the shared logger chain also scrubs info.headers and info.metadata.headers so other log sites are covered.

Header names stay in logs with values replaced by [REDACTED]. Redaction applies only to top-level headers shapes documented in code. Integration tests cover the helper, all log levels, non-mutation of caller metadata, and end-to-end requests.

Reviewed by Cursor Bugbot for commit 1af85c7. Bugbot is set up for automated code reviews on this repo. Configure here.

The request logger attached the full inbound header map to its record, so
x-api-key was written verbatim to application-%DATE%.log and to stdout on
every request. Both transports are pinned to 'debug', so APP.LOG_LEVEL did
not suppress it.

Redact at the call site and again in the shared winston format chain, so a
future record that carries a header map cannot reintroduce the leak.
Authorization, proxy-authorization, cookie and set-cookie are covered too;
their values no longer appear in verbose request logs.
@TheLastCicada
TheLastCicada deployed to windows-code-signing August 12, 2026 19:35 — with GitHub Actions Active
@TheLastCicada
TheLastCicada merged commit a485ebb into v2-rc2 Aug 12, 2026
49 of 51 checks passed
@TheLastCicada
TheLastCicada deleted the fix/redact-api-key-in-request-log branch August 12, 2026 21:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant