widen the publish secret gate from 1 credential format to 7
Last tick I named this narrow and deliberately left it, reasoning that public/ is a tree we GENERATE so the only realistic leak is the Mapbox token the build writes. That reasoning was wrong: 855 files under public/ are TRACKED, 95 of them text - boundaries, people-group data, brand assets, leaf pages - curated and committed by hand. A hand-committed file is exactly where a pasted credential lands, and this tree is rsynced to a PUBLIC catalog. Measured before fixing: a planted ghp_ PAT and an AKIA AWS key both passed as 'clean - 0 findings'. Now covers GitHub PATs, Slack tokens, AWS access keys, private-key blocks, literal Bearer tokens and Coolify token assignments alongside the original Mapbox JWT. Each entry matches a KNOWN credential FORMAT rather than guessing from entropy - an entropy rule over this tree would fire on every SRI hash and base64 image and be ignored. Negative-tested all 7: each plants its credential in public/ and is CAUGHT by name; the clean tree still passes with 0 findings across 855 files; the file is byte-identical after testing. The gate runs inside build:unlocked, so every build and CI run enforces it. Also caught before shipping: (?i:...) is Python/PCRE, not JavaScript - written inline it throws 'Invalid regular expression' and takes the whole gate down. Uses a trailing /i flag instead.
This commit is contained in:
parent
ea53a2e19a
commit
1e30c678a9
1 changed files with 20 additions and 1 deletions
|
|
@ -63,7 +63,26 @@ const LEAK = [
|
|||
[/\beco-dx\b/, 'internal folder name (eco-dx)'], [/qa-cycle/, 'internal folder name (qa-cycle)'], [/\b_archive\b/, 'internal folder name (_archive)'],
|
||||
[/localhost:(5174|3001|3033)\b/, 'dev-server URL'],
|
||||
]
|
||||
const SECRET = [[/\b(pk|sk|tk)\.eyJ[A-Za-z0-9_-]{20,}/, 'JWT-shaped Mapbox token']]
|
||||
// ★ WIDENED 2026-09-21. This held ONE pattern (the Mapbox JWT), on the reasoning that public/
|
||||
// is a tree we GENERATE, so the only realistic leak is the token the build itself writes.
|
||||
// THAT REASONING WAS WRONG: 855 files under public/ are TRACKED, 95 of them text — boundaries,
|
||||
// people-group data, brand assets, leaf pages — hand-curated and committed by a human. A
|
||||
// hand-committed file is exactly where a pasted credential lands, and this tree is rsynced to a
|
||||
// PUBLIC catalog. Measured before fixing: a planted `ghp_…` PAT and an `AKIA…` AWS key both
|
||||
// passed as "clean — 0 findings".
|
||||
// Each entry identifies a KNOWN credential FORMAT rather than guessing from entropy: an
|
||||
// entropy rule over this tree would fire on every SRI hash and base64 image and be ignored.
|
||||
const SECRET = [
|
||||
[/\b(pk|sk|tk)\.eyJ[A-Za-z0-9_-]{20,}/, 'JWT-shaped Mapbox token'],
|
||||
[/\bgh[pousr]_[A-Za-z0-9]{36,}/, 'GitHub personal-access token'],
|
||||
[/\bxox[abprs]-[A-Za-z0-9-]{10,}/, 'Slack token'],
|
||||
[/\bAKIA[0-9A-Z]{16}\b/, 'AWS access-key id'],
|
||||
[/-----BEGIN (RSA |EC |OPENSSH |PGP )?PRIVATE KEY-----/, 'private-key block'],
|
||||
[/\bBearer\s+[A-Za-z0-9_\-.]{30,}/, 'literal Bearer token'],
|
||||
// NOTE the trailing /i — JavaScript has no inline (?i:…) group (that is Python/PCRE);
|
||||
// written that way it throws "Invalid regular expression" and takes the whole gate down.
|
||||
[/coolify[^\n]{0,40}?[=:]\s*["']?[A-Za-z0-9_-]{32,}/i, 'Coolify API token'],
|
||||
]
|
||||
for (const f of files) {
|
||||
const ext = extname(f.rel).toLowerCase(), base = f.rel.split('/').pop()
|
||||
if (base === '.env' || /^\.env\./.test(base)) hit(findings, 'secrets', f.rel, '.env file')
|
||||
|
|
|
|||
Loading…
Reference in a new issue