Skip to main content

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_statement logs slow queries in full, and auto_explain logs 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.