history-leak-scan — deterministic secret scanner
The engine is pm-secretscan.js (portable Node, zero deps). It streams
git log -p --all (history) or git diff --cached (staged) and flags added
lines against per-provider regexes (17 rules) + a sensitive-filename rule
(.env*, .pem/.p12/.pfx, id_rsa, *_keys.env — a file of that NAME should never
be committed, whatever it contains; example/sample/template/fixture names
exempt) + a generic high-entropy-assignment detector + a weak-password rule,
with token-level placeholder suppression.
Commands
- Full-history scan (one or more repos):
node pm-secretscan.js --history <repo> [<repo>...]Exit 1 if any finding, 0 if clean. Redacts matched tokens in output. - Staged scan (what commit-gate runs):
node pm-secretscan.js --staged <repo> - Self-test (part of the definition of done):
node pm-secretscan.js --canaryPlants real-format secrets + placeholders in a throwaway repo, asserts ≥7 real caught and 0 false positives. MUST printPASSbefore you trust a scan result — an unverified gate is theater.
When invoked
- Run
--canaryfirst if the scanner was touched since last use; paste the PASS line. Never report a "clean" scan from an unverified scanner. - Enumerate the repos to scan (
find <root> -name .git -type d). Scan all with--history. - Triage every finding by READING the actual line — distinguish a live credential from a guarded dev-default or an example. Do NOT auto-rotate; report, and on a real leak point at secret-rotation + the secrets-inventory.
- Report outcome-first: repos clean vs. findings, each finding as file@commit + rule + redacted snippet, and the honest severity.
Known limits
- Catches secrets that were COMMITTED. Files that were always gitignored
(
.env, key files) are correctly out of history — verify they were never committed by a clean history scan, not by their current absence. - Entropy detection can miss bespoke low-entropy formats — that's why the
per-provider regexes exist; extend
RULESwhen a new provider appears (and add a canary fixture for every new rule — a rule without a canary is unverified). - No live credential verification (TruffleHog-style API checks) BY DESIGN: that would transmit candidate secrets to provider endpoints. Triage of live-vs-dev-default stays a human/model judgment step.
- Heuristic exemption also covers rendered-doc twins under
docs/*.html(a generated HTML render of an exempt.md); strong provider/private-key rules still apply there. Diff headers with special-char filenames (git C-quotes them, e.g. an em-dash path) are unquoted before the filename rules run — otherwise every rule keyed on the filename would silently no-op. - Redaction shows first-4 + last-2 of long tokens; still treat output as sensitive.