From 1e30c678a92f0d69e232ba595eac6602ebd6c622 Mon Sep 17 00:00:00 2001 From: doxa-maps-88655c70 <88627970+doxa-maps-88655c70@users.noreply.github.com> Date: Mon, 21 Sep 2026 21:57:39 +0000 Subject: [PATCH] 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. --- internal/public-hygiene-check.mjs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/internal/public-hygiene-check.mjs b/internal/public-hygiene-check.mjs index 99a77c351..cfe826775 100644 --- a/internal/public-hygiene-check.mjs +++ b/internal/public-hygiene-check.mjs @@ -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')