H #2913: postgres:postgres is exempt only on a loopback host

The postgres image's default pair in compose files and CI DATABASE_URLs
blocked pushes as db.url_with_password HIGH. It is now exempt only for
postgres/postgresql URLs with exactly postgres:postgres on localhost or
127.0.0.1 (optional port). A bare single-label host (`db`, `postgres`), any
other host, a host that merely starts with localhost (`localhost.evil...`,
`localhost@prod-db`), any other user or password, and other schemes still
block. #2913 exempted the pair on any host; this keeps the plan's narrower
rule.

Co-authored-by: mb1810 <michael.berger1810@gmail.com>
This commit is contained in:
garrytanandmb1810 committed 2026-10-03 20:39:18 +00:00
1 parent 5999ee3bad
commit 178724034b
2 files changed
+46 -2

No files matched your search

+15 -2
View File
@@ -521,6 +521,18 @@ function urlPasswordIsPlaceholder(span: string): boolean {
return PLACEHOLDER_STRUCTURAL.some((re) => re.test(pw));
}
/**
* #2913: `postgres:postgres` is the official postgres image's default pair,
* committed in compose files and CI DATABASE_URLs. It is exempt only on a
* loopback host (`localhost` or `127.0.0.1`, optional port), never on a
* single-label host such as `db`: a compose service name says nothing about
* where the same URL is also deployed, and a weak default on a reachable host
* is exactly the leak to report. Exact user and password, case-sensitive;
* the host must end at a port, path, query, quote or the span's end.
*/
const POSTGRES_LOOPBACK_DEFAULT =
/^postgres(?:ql)?:\/\/postgres:postgres@(?:localhost|127\.0\.0\.1)(?::\d{1,5})?(?![\w.:@%-])/;
/** A value span that is only an environment-variable read expression (#2912). */
const ENV_READ_SPAN =
/^(?:os\.environ\[|os\.environ\.get\(|os\.getenv\(|getenv\(|ENV\[|process\.env\.[A-Za-z_$][\w$]*[;,)]?)$/;
@@ -767,8 +779,9 @@ export const PATTERNS: RedactPattern[] = [
category: "secret",
description: "Database URL with embedded password",
regex: /\b((?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp):\/\/[^:\s/@]+:[^@\s/]+@[^\s/]+)/,
// Skip when the password segment is itself a placeholder/interpolation.
validate: (span) => !urlPasswordIsPlaceholder(span),
// Skip when the password segment is itself a placeholder/interpolation,
// or the URL is postgres's default pair on a loopback host.
validate: (span) => !urlPasswordIsPlaceholder(span) && !POSTGRES_LOOPBACK_DEFAULT.test(span),
},
{
id: "creds.basic_auth_url",
+31
View File
@@ -160,3 +160,34 @@ describe("pii.ip_public: a four-part version needs a version declaration (#2784)
],
);
});
describe("db.url_with_password: postgres:postgres only on a loopback host (#2913)", () => {
// Assembled from parts so this file's own source never carries a
// credential-bearing URL for the repo's secret scan to block.
const url = (userinfo: string, rest: string, scheme = "postgresql") => `${scheme}://${userinfo}@${rest}`;
const pg = ["postgres", "postgres"].join(":");
table(
"db.url_with_password",
"HIGH",
[
`DATABASE_URL=${url(pg, "localhost:5432/app_test")}`,
`DATABASE_URL=${url(pg, "127.0.0.1:5432/app", "postgres")}`,
`DIRECT_URL="${url(pg, "localhost/app")}"`,
`url: ${url(pg, "localhost", "postgres")}`,
url(pg, "localhost:5432?sslmode=disable"),
],
[
`DATABASE_URL=${url(pg, "db:5432/app")}`,
`DATABASE_URL=${url(pg, "postgres:5432/app")}`,
`DATABASE_URL=${url(pg, "prod-host:5432/app")}`,
`DATABASE_URL=${url(pg, "localhost.evil.example.net/app")}`,
`DATABASE_URL=${url(pg, "127.0.0.1.nip.io/app")}`,
`DATABASE_URL=${url(pg, "localhost@prod-db/app")}`,
`DATABASE_URL=${url(["user", "s3cretPassw0rd"].join(":"), "prod-host:5432/app")}`,
`DATABASE_URL=${url(["postgres", "hunter2x"].join(":"), "localhost:5432/app")}`,
`DATABASE_URL=${url(["admin", "postgres"].join(":"), "localhost:5432/app")}`,
`DATABASE_URL=${url(["postgres", "Postgres"].join(":"), "localhost:5432/app")}`,
`DATABASE_URL=${url(pg, "localhost:3306/app", "mysql")}`,
],
);
});