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:
doxa-maps-88655c70 2026-09-21 21:57:39 +00:00
parent ea53a2e19a
commit 1e30c678a9

View file

@ -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')