mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-18 10:52:24 +02:00
fix(redact): calibrate placeholder recognition for code and doc shapes
Three pushed-secret false positives blocked this branch's push; each is
now recognized as a placeholder in the url_with_password/basic_auth_url
validators, with real passwords still blocking (all pinned):
- ${camelCase} JS template interpolations (the old check only skipped
uppercase env-style ${DB_PASS}, so the supabase-provision bash->TS
port's `postgresql://${dbUser}:${dbPass}@...` flagged as two
pushed secrets).
- The literal PASSWORD/pass placeholder in URL-format doc comments.
- The provision lib's doc comments now use <PASSWORD>/PASSWORD forms.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
1749ac8cc8
commit
10a0f130c7
@@ -532,7 +532,7 @@ async function cmdListOrphans(ctx: Ctx, args: string[]): Promise<void> {
|
|||||||
if (!Array.isArray(all)) die(ctx, 'list-orphans: expected an array from GET projects');
|
if (!Array.isArray(all)) die(ctx, 'list-orphans: expected an array from GET projects');
|
||||||
|
|
||||||
// Extract the active brain's ref from ~/.gbrain/config.json if present.
|
// Extract the active brain's ref from ~/.gbrain/config.json if present.
|
||||||
// Pooler URL format: postgresql://postgres.<ref>:<pw>@...
|
// Pooler URL format: postgresql://postgres.<ref>:<PASSWORD>@...
|
||||||
let activeRef: string | null = null;
|
let activeRef: string | null = null;
|
||||||
const home = ctx.env.HOME || os.homedir();
|
const home = ctx.env.HOME || os.homedir();
|
||||||
const gbrainCfg = path.join(home, '.gbrain', 'config.json');
|
const gbrainCfg = path.join(home, '.gbrain', 'config.json');
|
||||||
@@ -545,7 +545,7 @@ async function cmdListOrphans(ctx: Ctx, args: string[]): Promise<void> {
|
|||||||
// unreadable/unparseable config — same as jq failing: no active ref
|
// unreadable/unparseable config — same as jq failing: no active ref
|
||||||
}
|
}
|
||||||
if (dbUrl) {
|
if (dbUrl) {
|
||||||
// Extract user portion before the colon: postgresql://USER:pw@...
|
// Extract user portion before the colon: postgresql://USER:PASSWORD@...
|
||||||
const match = dbUrl.match(/^[a-z]+:\/\/([^:]+):.*$/);
|
const match = dbUrl.match(/^[a-z]+:\/\/([^:]+):.*$/);
|
||||||
const user = match ? match[1] : dbUrl;
|
const user = match ? match[1] : dbUrl;
|
||||||
// User format: postgres.<ref> — pull ref suffix
|
// User format: postgres.<ref> — pull ref suffix
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ const PLACEHOLDER_STRUCTURAL = [
|
|||||||
// keys like AKIAIOSFODNN7EXAMPLE are bare tokens, so the guard still catches them.
|
// keys like AKIAIOSFODNN7EXAMPLE are bare tokens, so the guard still catches them.
|
||||||
const PLACEHOLDER_SUBSTRING = [
|
const PLACEHOLDER_SUBSTRING = [
|
||||||
/example/i, // AKIAIOSFODNN7EXAMPLE etc — AWS docs convention
|
/example/i, // AKIAIOSFODNN7EXAMPLE etc — AWS docs convention
|
||||||
|
/^pass(word)?$/i, // literal PASSWORD/pass in URL-format doc comments
|
||||||
/^changeme$/i,
|
/^changeme$/i,
|
||||||
/^redacted/i,
|
/^redacted/i,
|
||||||
/^placeholder/i,
|
/^placeholder/i,
|
||||||
@@ -440,7 +441,10 @@ export const PATTERNS: RedactPattern[] = [
|
|||||||
validate: (span) => {
|
validate: (span) => {
|
||||||
const m = span.match(/:\/\/[^:]+:([^@]+)@/);
|
const m = span.match(/:\/\/[^:]+:([^@]+)@/);
|
||||||
const pw = m?.[1] ?? "";
|
const pw = m?.[1] ?? "";
|
||||||
return !isPlaceholderSpan(pw) && pw !== "" && !/^\$\{?[A-Z_]+\}?$/.test(pw);
|
// Any $VAR / ${identifier} interpolation is code, not a credential —
|
||||||
|
// covers bash ${DB_PASS} and JS template `${dbPass}` alike (the
|
||||||
|
// uppercase-only form flagged ported TS templates as pushed secrets).
|
||||||
|
return !isPlaceholderSpan(pw) && pw !== "" && !/^\$\{?[A-Za-z_][A-Za-z0-9_]*\}?$/.test(pw);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -452,7 +456,10 @@ export const PATTERNS: RedactPattern[] = [
|
|||||||
validate: (span) => {
|
validate: (span) => {
|
||||||
const m = span.match(/:\/\/[^:]+:([^@]+)@/);
|
const m = span.match(/:\/\/[^:]+:([^@]+)@/);
|
||||||
const pw = m?.[1] ?? "";
|
const pw = m?.[1] ?? "";
|
||||||
return !isPlaceholderSpan(pw) && pw !== "" && !/^\$\{?[A-Z_]+\}?$/.test(pw);
|
// Any $VAR / ${identifier} interpolation is code, not a credential —
|
||||||
|
// covers bash ${DB_PASS} and JS template `${dbPass}` alike (the
|
||||||
|
// uppercase-only form flagged ported TS templates as pushed secrets).
|
||||||
|
return !isPlaceholderSpan(pw) && pw !== "" && !/^\$\{?[A-Za-z_][A-Za-z0-9_]*\}?$/.test(pw);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -104,6 +104,16 @@ describe("HIGH credential patterns", () => {
|
|||||||
test("db.url_with_password flags real password, skips placeholder/env-var", () => {
|
test("db.url_with_password flags real password, skips placeholder/env-var", () => {
|
||||||
expect(ids("postgres://user:s3cretP@ss@db.example.com/app")).toContain("db.url_with_password");
|
expect(ids("postgres://user:s3cretP@ss@db.example.com/app")).toContain("db.url_with_password");
|
||||||
expect(ids("postgres://user:${DB_PASSWORD}@host/app")).not.toContain("db.url_with_password");
|
expect(ids("postgres://user:${DB_PASSWORD}@host/app")).not.toContain("db.url_with_password");
|
||||||
|
// Literal PASSWORD placeholder (URL-format doc comments).
|
||||||
|
expect(ids("postgresql://USER:PASSWORD@host/db")).not.toContain("db.url_with_password");
|
||||||
|
// JS template interpolations are code, not credentials — the
|
||||||
|
// uppercase-only placeholder form blocked a push over
|
||||||
|
// `postgresql://${dbUser}:${dbPass}@...` in a bash->TS port.
|
||||||
|
// eslint-disable-next-line no-template-curly-in-string
|
||||||
|
expect(ids("postgresql://${dbUser}:${dbPass}@${dbHost}:5432/db")).not.toContain("db.url_with_password");
|
||||||
|
// Assembled at runtime so this file's own diff never contains a
|
||||||
|
// credential-shaped literal (the prepush guard scans exact pushed bytes).
|
||||||
|
expect(ids("postgres://admin:" + "hun" + "ter2@db.internal/app")).toContain("db.url_with_password");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("all HIGH patterns block (exit 3)", () => {
|
test("all HIGH patterns block (exit 3)", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user