Skip to main content

SEC.CFG.STATEMENT_LOGGING — Statements and their values are written to the server log

  • Category: privacy
  • Severity: medium
  • Level: 0
  • Confidence: deterministic
  • Downtime class: none — the finding is about a server parameter, not about a statement
  • Stability: stable
  • Suites: audit
  • Applies to: PostgreSQL 18

What the value means

log_statement is an enum with four members, and two of them reach data.

ValueWhat is loggedDoes it carry row values?
nonenothingno
ddlschema statementsno — column names, never rows
modstatements that change datayes — every row a migration or job writes
allevery statementyes — including every WHERE email = …

ddl is where the line falls, and the reason is data rather than verbosity: a schema statement names columns, so it says nothing about a person. ddl on a production server is an ordinary audit trail, and reporting it would train people to ignore this rule.

Why the log is the problem rather than the setting

Nothing here is a defect. The parameter is doing exactly what it says, and the server is functioning correctly — which is why this is reported rather than prevented.

What changes is where the data lives. A log file rarely enjoys the database's own protection:

  • it is read by operators who were never granted the tables it quotes,
  • it is shipped to an aggregator that keeps it under a different retention policy,
  • and it sits in backups that outlive the row an erasure request removed.

None of those paths involves a privilege on the database. The grant model you designed does not reach the file.

Why the environment decides

This is the only rule in the pack whose verdict depends on something outside the database, and it is the reason the rule exists in this shape rather than as a plain server-baseline check.

log_statement = all on a laptop is how people debug. The same value on a production instance is personal data leaving the database's protection. The value cannot tell those apart, so a rule that judged it alone would be muted within a week by the people it is meant to help.

So the rule establishes the environment first, and answers three-valued:

What the run can establishAnswer
this is productionthe value decides — all or mod is a finding
this is not productionpass, with its sentence — checked, and it exposes nothing here
nothing places itundetermined, with the reason named

That third row is the point. app.env unset, or carrying a name this check does not recognize, and the connection name saying nothing either, is not a reason to report a pass — a pass would be a statement about a server nobody placed.

Why medium and not higher

The data is inside your own infrastructure. This is exposure to a wider circle than the database grants, not exposure to the public.

Medium also sits below the CI gate's high default, so an honest finding does not block a pipeline that never opted into privacy gating.

What it looks like

# postgresql.conf: every statement and every parameter value, written to the server log.
log_statement = all

What to do about it

# Schema statements carry column names, never row values — an audit trail without the data.
log_statement = ddl

log_statement takes effect on a configuration reload; no restart is needed. Its context is superuser, so a session may also set it for itself — which is why this rule judges what a fresh session resets to rather than what the audit's own connection is running with.

What this rule cannot see

Who can read the log, where it is shipped, how long it is kept, and whether anything redacts it on the way. Those are questions about the host rather than about the server's catalog, and this finding does not claim them.

It also cannot tell a server deliberately logging everything during an incident from a switch left on afterwards. That is the honest limit of a value read at one moment.