· 4 min read
How to Catch API Keys Before You Commit Them
Heshan Fernando
Co-founder & COO
The classic version of this story: someone hard-codes a key to test something locally, means to remove it before committing, gets interrupted, and commits the whole file. It sits in the repository for eleven months until a scanner finds it, and by then the key is in a public git history that has been cloned an unknown number of times.
The uncomfortable part isn’t the mistake. It’s that the mistake is invisible — a credential in a config file looks exactly like every other string in a config file.
What a secret scanner is looking for
Two signals, used together.
Known prefixes. Many providers deliberately give their credentials recognisable shapes — a fixed prefix followed by a fixed-length body. That structure exists partly so scanners can spot them. A string beginning AKIA or ghp_ is almost certainly what it looks like.
Entropy. Randomness measured in bits per character. Ordinary identifiers and English words score around three; base64-encoded random data scores close to five. A 40-character string with high entropy sitting in a config file is worth flagging even when nothing recognises its format.
Neither signal is sufficient alone. Prefixes miss anything custom or self-issued; entropy flags every hash, UUID and test fixture in the codebase. Together they’re good enough to be useful, which is all a pre-commit check needs to be.
Why people get stuck here
- Scanning after pushing. Most tooling runs in CI, which is after the secret is already in shared history.
- False positive fatigue. A scanner that flags every checksum gets muted, and then it flags a real key into a muted channel.
- Believing deletion is enough. Removing the line in a new commit leaves the old commit intact and fully readable.
- Not knowing what to rotate. A leaked key discovered weeks later means auditing what it had access to, which nobody has documented.
What good scanning looks like
It runs before the commit
The only place a scan fully prevents a leak is on your machine, before the object exists in shared history. Everything after that is detection and cleanup.
Sensitivity you can tune
High sensitivity for a repository that will go public; lower for one full of test fixtures and hashes. A fixed threshold is wrong for someone.
The scanned content stays local
A tool you paste credentials into should not transmit them anywhere. That sounds obvious and is worth checking, because it is exactly the tool where the failure would be worst.
| Signal | Catches | Misses |
|---|---|---|
| Known prefixes | Provider keys with fixed shapes | Custom or self-issued tokens |
| Entropy | Random-looking strings of any format | Low-entropy secrets like weak passwords |
| Filename rules | .env, id_rsa, key files | Secrets in unexpected files |
Common mistakes to avoid
- Committing first and scanning later, then treating the finding as a code-cleanup task rather than an incident.
- Rewriting history to remove a secret and considering the job finished — if it was ever pushed, assume it was captured, and rotate.
- Adding secrets to
.gitignoreafter they’ve been committed. Gitignore doesn’t affect files already tracked. - Storing production keys in a
.env.example“for convenience”. - Silencing a scanner globally because of test fixtures, rather than excluding the fixture directory.
How to do it with Secret Scanner
The Secret Scanner checks the file in front of you, in your browser, before it becomes a commit.
- Paste the file, diff, or config block you’re about to commit.
- Choose a sensitivity — high catches more and produces more false positives on hashes.
- Read every finding as if it were real: rotate first, remove second.
- Check the flagged false positives too; a fixture directory that scans clean today may not tomorrow.
If something has already been pushed, GitHub’s guidance on removing sensitive data covers history rewriting — but rotation comes first. Other privacy-focused tools are in the tools directory.
Frequently asked questions
A secret was committed. Is deleting the line enough?
No. Git history retains the old content, and anything pushed to a shared or public remote should be assumed captured within minutes. Rotate the credential immediately; rewriting history is cleanup, not remediation.
Why does it flag hashes and test data?
Because a checksum is statistically indistinguishable from a key. Any scanner sensitive enough to catch real secrets will flag some random-looking strings that aren’t. That trade-off is inherent, not a bug.
Is my code uploaded when I scan?
No. Scanning happens in the page. For a tool whose entire purpose is handling credentials, that’s the only defensible design.
Final thought
Treat every finding as a real leak until proven otherwise, and rotate before you investigate. The cost of rotating a key you didn’t need to is fifteen minutes; the cost of investigating first is however long the key stays live.