Tool
Secrets pre-commit check
A dependency-free script that scans staged changes for credential-shaped strings and blocks the commit before the value enters history.
What it catches
- Provider-issued keys with fixed shapes: AWS access key ids, GitHub, Slack and Stripe tokens, Google API keys.
- PEM private key blocks, including RSA, EC, OPENSSH and PGP headers.
- JSON Web Tokens, by their three base64url segments.
- Connection strings that carry an inline password, in any scheme.
- Assignments like password = "…" or api_key: "…" where the value is eight characters or more.
What it misses
- Passwords someone chose. There is no pattern for hunter2 and no entropy threshold that separates it from ordinary text.
- Anything already in history. It reads staged changes only, so it prevents the next leak rather than finding the last one.
- Encoded or encrypted values. Base64 a key and it no longer matches the shape it is recognised by.
- Secrets outside the diff: a screenshot, a stack trace, a log line, a value pasted into an issue.
- Values that look like placeholders. Anything containing example, changeme, your-, <…> or ${…} is skipped on purpose, which means a real key with EXAMPLE in it is skipped too.
Install
node tools/secrets-precommit/secrets-check.mjsWhat it is
One file, no dependencies, no network. It reads git diff --cached and looks at added
lines only — context and deletions are already in history or on their way out of it.
The check runs before the commit because that is the only point at which removing a secret is free. Once a commit is pushed, deleting the file changes the working tree and nothing else: the value is in history, and anyone who cloned in between has it.
Install
Add it as a hook:
ln -s ../../tools/secrets-precommit/secrets-check.mjs .git/hooks/pre-commit
Or run it in CI against a pull request’s diff.
What it printed on a test commit
Two credential-shaped values and two safe ones, staged together:
secrets-check: 2 possible secret(s) in staged changes
leak2.js
AWS access key id (AKIA…)
leak2.js
Connection string with password (post…)
It flagged the key and the inline password, and stayed quiet about
process.env.API_KEY and a sk_live_${KEY} template literal — neither is a secret, both
look like one to a naive grep.
It also stayed quiet about AKIAIOSFODNN7EXAMPLE, which is AWS’s documented example
key. That is the placeholder filter doing its job, and it is also the clearest example of
this tool’s central trade-off.
Watch out. The output never prints the matched value, only its first four characters. Hook output lands in terminals, CI logs and screenshots — a scanner that echoes the secret it found has moved the secret somewhere new.
The trade-off, stated plainly
Every secret scanner sits on one dial: how much noise will people tolerate before they stop reading the output.
Too strict and every hash, minified bundle and UUID is a finding. People learn to pass
--no-verify reflexively, and then the hook is worse than nothing, because the team
believes it is protected.
So this errs quiet. It matches shapes that are almost certainly credentials and skips anything that looks like an example. That means it will miss real secrets — the list above is the honest version of which ones.
It is a seatbelt, not a security control. The control is rotating anything that was ever pushed.
