fix(db): disable Sequelize default console.log query logging to stop secret leakage to stdout#144
Merged
Conversation
…secret leakage to stdout Sequelize's default `logging` is `console.log`, which dumps every executed SQL statement — INCLUDING bound parameter values — to stdout. In production that means a query like `SELECT * FROM "dbo"."ApiKey" WHERE "akKey" = $1` followed by the bound array containing the raw authKey lands in the operator's log stream alongside our structured JSON output. Two problems with that: 1. **Secret leakage**: pino's redact paths only inspect HTTP request shapes (`req.headers.authkey`, etc.). They never see Sequelize's SQL strings, so the bound authKey value bypasses every redaction layer and lands raw in stdout. The output goes wherever stdout goes — terminal in dev, Docker logs in prod, then a log shipper, then whatever the operator's log retention is set to. 2. **Format mixing**: structured pino JSON lines and Sequelize's free-form multi-line console output share the same fd. Log shippers (Vector, Loki, CloudWatch) that parse line-by-line as JSON either fail to parse the Sequelize lines or fall back to a "raw" representation that defeats the whole point of structured logging. Default to `logging: false` — Sequelize emits nothing. Operators who actually need query logs for debugging can set `DB_LOG_QUERIES=1`, in which case queries route through pino at debug level (silent at the default LOG_LEVEL=info, visible when LOG_LEVEL=debug, and never emitted as bare console.log even when visible). Documented in .env.example with the threat-model note so operators flipping it on for debugging understand it can still echo parameter values into the structured log. No test added: the default-disabled behavior is verified by every existing test (no Sequelize spam in vitest output); the opt-in path is exercised only via env var at module load, which doesn't compose well with vitest's module cache. The change is a 1-line behavior flip with a documented rollback (set DB_LOG_QUERIES=1). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #143.
Summary
Sequelize's default
loggingisconsole.log. Without overriding it, every executed SQL statement — and every bound parameter value — is dumped to stdout. That means a query likeSELECT * FROM "dbo"."ApiKey" WHERE "akKey" = $1followed by the bound array containing the raw authKey lands in stdout alongside our structured JSON output. pino's redact paths only inspect HTTP request shapes, so the Sequelize SQL path bypasses every redaction layer and the secret ends up in container logs / log shipper / retention.Default
logging: false. Operators who need query logs for targeted debugging setDB_LOG_QUERIES=1, in which case Sequelize queries are routed through pino atdebuglevel (silent at the defaultLOG_LEVEL=info, visible atLOG_LEVEL=debug, never emitted as bare console.log).Documented in
.env.examplewith the threat-model note that the opt-in mode can still echo parameter values into the structured log.Test plan
npm run lint— cleannpm test— 515 passed, 15 skipped (default-disabled is verified by every existing test no longer being noisy with Sequelize chatter; the opt-in path requires env var at module load, which doesn't compose well with vitest's module cache)DB_LOG_QUERIES=1)Proudly Made in Nebraska. Go Big Red! 🌽 https://xkcd.com/2347/