Visitar URL original
Recognize a missing Postgres database by SQLSTATE by gaborbernat · Pull Request #5312 · trufflesecurity/trufflehog · GitHub
Skip to content

Recognize a missing Postgres database by SQLSTATE - #5312

Merged
mariduv merged 1 commit into
trufflesecurity:mainfrom
gaborbernat:fix/postgres-default-database
Sep 22, 2026
Merged

mariduv merged 1 commit into
trufflesecurity:mainfrom
gaborbernat:fix/postgres-default-database

Conversation

@gaborbernat

@gaborbernat gaborbernat commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

A Postgres URI that names no database gets reported as a verification error even when the credentials are good.

The detector already treats "database does not exist" as proof of a working credential, since the server only looks the database up once authentication has passed. The check builds that message from a database name, and when the connection string supplies none it assumes the default is postgres. Here is the exchange, credential masked. Note the trailing slash: the string names no database.

  dialled   postgresql://svc_reports:••••••••@db-prod-07.internal:5432/
                                                                      ↑ no database given
  libpq     omits the parameter, so the server fills it in
  server    pq: database "svc_reports" does not exist
                          ↑ the user name, not "postgres"
  check     looks for: database "postgres" does not exist   →   no match

Postgres defaults the database to the user name: port->database_name = pstrdup(port->user_name) in backend_startup.c, under the comment "The database defaults to the user name", matching the StartupMessage description in the protocol documentation. The driver plays no part in it: lib/pq copies the options it was given into the startup packet and leaves an absent database absent, with no default of its own anywhere in the package.

Ordering is what makes the reply safe to read as success. PerformAuthentication runs before the lookup that raises database "%s" does not exist, so reaching that error means the credentials were accepted. That is the same conclusion the existing code states in its own comment.

SQLSTATE settles this without guessing a name. 3D000 is invalid_catalog_name, both drivers expose it, lib/pq by name and pgx on its error type, and it carries the same meaning whatever the database was called. It also holds on a server running a non-English lc_messages, where comparing message text cannot work at all.

Message matching stays as a fallback, since a connection pooler can relay the failure without a SQLSTATE, and it now expects the name the server would have used. That closes a second gap in the same helper: an error naming some other missing database used to satisfy the check and mark the credential verified.

The postgres default dates from #2314 in January 2024 and reached the pgx path through #5217. Anyone scanning connection strings written without an explicit database loses those verifications today, which is how this surfaced: a corpus of 170 Postgres credentials where 139 omit the database reported every one of them as unverifiable.

Postgres names the database in "database %s does not exist", and defaults
it to the user name when the connection string carries none. The check
assumed that default was "postgres", so it never matched for a URI without
a database, and a credential the server had already authenticated was
reported as a verification error instead of a live secret.

Prefer SQLSTATE 3D000, which both drivers expose and which needs no name at
all. It also survives a server running with a non-English lc_messages,
where matching on message text cannot work.

Message matching stays as a fallback for proxies that relay the failure
without a SQLSTATE, now expecting the same name the server used.
@gaborbernat
gaborbernat requested a review from a team September 11, 2026 17:50
@gaborbernat
gaborbernat requested a review from a team as a code owner September 11, 2026 17:50
@gaborbernat
gaborbernat marked this pull request as draft September 11, 2026 17:51
@gaborbernat
gaborbernat marked this pull request as ready for review September 11, 2026 21:08

@shahzadhaider1 shahzadhaider1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@MuneebUllahKhan222 MuneebUllahKhan222 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!
Just one small non-blocking comment.

switch pgErr.Code {
case "28P01": // invalid_password
return false, nil
case "3D000": // invalid_catalog_name — authenticated, DB missing

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Remove this case statement so the invalid_catalog_name check happens in only 1 place i.e isErrorDatabaseNotFound

@mariduv
mariduv merged commit ff8d20c into trufflesecurity:main Sep 22, 2026
17 checks passed
@gaborbernat
gaborbernat deleted the fix/postgres-default-database branch October 3, 2026 15:12
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.

4 participants