SEC.CFG.STATEMENT_LOGGING_SECRETS — Passwords and tokens are going into the server log
- Category: security
- Severity: high
- Level: 0
- Confidence: deterministic
- Downtime class: none — the finding is about a server setting, not about a statement
- Stability: stable
- Suites: audit
- Applies to: PostgreSQL
The half that was missing
MySQL's general_log had a hardening view and PostgreSQL's log_statement had a privacy one. Same
concern, opposite categories, one per engine — which nobody decided; it is what two tickets written
months apart produced.
The consequence was invisible in the way that matters. A team hardening a PostgreSQL server filters
on --category=security, and log_statement = all never reached them — although
CREATE ROLE app LOGIN PASSWORD 'hunter2' goes into that log with the password, exactly as it does
on MySQL. Nothing was red. The report simply did not contain a finding, and that reads as a clean
server.
What ends up in the file
At all, every statement — reads included, so a token compared in a WHERE clause is logged as
well as one that was written.
At mod, every statement that changes data. That is narrower and still logs every value a migration
or a job writes.
ddl logs schema changes without values, and none logs nothing. Neither is reported.
PostgreSQL does not redact. MySQL rewrites passwords before logging in some logs; PostgreSQL
has no equivalent for log_statement. The statement is written as it was executed.
Why High rather than Medium
Its privacy sibling reports the same setting at Medium, and the two are measuring different damage. Personal data in a log is a retention and erasure problem: serious, and slower. A credential in a log is valid until somebody rotates it, and nobody rotates what they do not know leaked.
Not limited to production, unlike its sibling
The privacy view only reports on production, because a laptop logging its own queries is how people debug. This one does not inherit that.
A password in a log is a leaked credential wherever the log is. A development database that shares a password with anything else has already lost it, and "it was only staging" is the sentence that follows every credential incident.
What it does not claim
- It reads the running value, not
postgresql.conf. A change not yet reloaded is invisible here. - It cannot see where the log goes — a file only root can read is a different exposure from one shipped to an aggregator.
- It judges only
log_statement. PostgreSQL can put statement text into a log through other settings:log_min_duration_statementlogs slow queries in full, andauto_explainlogs them with their parameters. Neither is read here, so a quiet verdict from this rule is not a statement about the server's logging as a whole.
What it looks like
# postgresql.conf: every statement, and every value in it, into the log.
log_statement = 'all'
What to do about it
# Schema changes keep an audit trail; row values never reach the log.
log_statement = 'ddl'
When it is a false positive
mod rather than all narrows it to writes, which is a real mitigation and still logs every value a migration or job writes. ddl and none carry no values and are not reported. A short debugging window is legitimate and indistinguishable from a forgotten switch, which is the point: the cost is silent and grows with uptime.