fix(redact): block real all-caps URL passwords, not just shape-match

urlPasswordIsPlaceholder skipped any password matching /^[A-Z][A-Z0-9_]*$/,
so a real DSN like postgres://admin:PROD2026SECRET@db-prod.internal/app slipped
the HIGH pre-push block. Replace the shape rule with an anchored, exact-match
set of doc-convention placeholder tokens (PASSWORD, PASS, CHANGEME, ...),
compared case-sensitively and never as a substring (PROD2026SECRET must not
match SECRET). The USER:PASSWORD doc convention still suppresses; real all-caps
and lowercase passwords block. Regression cases pinned both directions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-16 10:14:32 -07:00
co-authored by Claude Fable 5
parent 410b4928e7
commit 57a1d957f4
2 changed files with 29 additions and 6 deletions
+22 -6
View File
@@ -268,17 +268,33 @@ export function insideUuid(match: RegExpExecArray): boolean {
// alike (the identifier-only form flagged the DSN-encoding call site as a
// pushed secret). Bare `$word` stays uppercase-only: `$hunter2` must block.
const INTERPOLATED_PASSWORD_RE = /^(\$\{.+\}|\$[A-Z_][A-Z0-9_]*)$/;
// URL-password placeholders are matched by EXACT token, never by shape or
// substring. A shape rule (`/^[A-Z][A-Z0-9_]*$/`) waved through real all-caps
// secrets like `PROD2026SECRET`; a substring rule would let `PROD2026SECRET`
// slip because it contains `SECRET`. So this is an anchored, hand-curated set
// of the doc-comment conventions (postgres://USER:PASSWORD@host) only. Compared
// case-sensitively against the raw span: the convention is ALL CAPS, and a
// lowercase `password`/`pass` at this position is a real (terrible) credential
// that must still block.
const URL_PASSWORD_PLACEHOLDER_WORDS = new Set([
"PASSWORD",
"PASS",
"PASSWD",
"YOUR_PASSWORD",
"DB_PASSWORD",
"MY_PASSWORD",
"CHANGEME",
"CHANGE_ME",
"PLACEHOLDER",
"REDACTED",
"EXAMPLE",
]);
function urlPasswordIsPlaceholder(span: string): boolean {
const m = span.match(/:\/\/[^:]+:([^@]+)@/);
const pw = m?.[1] ?? "";
if (pw === "") return true;
if (INTERPOLATED_PASSWORD_RE.test(pw)) return true;
// URL-password position is STRICTER than generic placeholder detection.
// Doc-comment convention writes placeholders in ALL CAPS
// (postgres://USER:PASSWORD@host); a lowercase `password` or `pass` at
// this position is a real (terrible) credential and must block — the
// case-insensitive isPlaceholderSpan words would wave it through.
if (/^[A-Z][A-Z0-9_]*$/.test(pw)) return true;
if (URL_PASSWORD_PLACEHOLDER_WORDS.has(pw)) return true;
return PLACEHOLDER_STRUCTURAL.some((re) => re.test(pw));
}
+7
View File
@@ -134,6 +134,13 @@ describe("HIGH credential patterns", () => {
expect(ids("https://root:" + "pa" + "ss@127.0.0.1/")).toContain("creds.basic_auth_url");
// Structural placeholders still suppress at the URL position.
expect(ids("postgres://user:<your-password>@host/db")).not.toContain("db.url_with_password");
// An ALL-CAPS password that is NOT an exact placeholder token is a real
// secret and must block — the pre-fix shape rule (/^[A-Z][A-Z0-9_]*$/) waved
// every all-caps password through. Substring of a placeholder word (SECRET)
// must not rescue it. Assembled at runtime so this file's own pushed bytes
// carry no live DSN shape.
expect(ids("postgres://admin:" + "PROD2026" + "SECRET@db-prod.internal/app")).toContain("db.url_with_password");
expect(ids("postgres://admin:" + "ADMIN" + "123@host/db")).toContain("db.url_with_password");
});
test("all HIGH patterns block (exit 3)", () => {