From 178724034bc597c0cbdd043108396b28b4bed12c Mon Sep 17 00:00:00 2001 From: garrytan Date: Sat, 3 Oct 2026 20:39:18 +0000 Subject: [PATCH] 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 --- lib/redact-patterns.ts | 17 ++++++++++++-- test/redact-context-exemptions.test.ts | 31 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/lib/redact-patterns.ts b/lib/redact-patterns.ts index b496bb5e3..cc065464a 100644 --- a/lib/redact-patterns.ts +++ b/lib/redact-patterns.ts @@ -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", diff --git a/test/redact-context-exemptions.test.ts b/test/redact-context-exemptions.test.ts index 874a08279..449355442 100644 --- a/test/redact-context-exemptions.test.ts +++ b/test/redact-context-exemptions.test.ts @@ -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")}`, + ], + ); +});