A Problem That Hasn't Gone Away
Hardcoded secrets — API keys, database passwords, cloud credentials pasted directly into source code — remain one of the most consistently reported findings across security audits of real-world repositories. It's not a problem caused by carelessness alone: it's the path of least resistance. Hardcoding const apiKey = "sk_live_..." works immediately, in local dev, in a demo, in a hotfix pushed at 11pm — and then it quietly survives into the next commit, and the one after that.
The fix isn't "be more careful." It's catching the pattern automatically, before it reaches a shared branch.
What a Hardcoded Secret Actually Looks Like
Most real secrets aren't random-looking strings with no structure — they have a recognizable shape, because providers design their token formats to be identifiable (partly so their own scanning partnerships, like GitHub's, can detect them):
| Provider | Format | Example shape |
|---|---|---|
| AWS Access Key ID | AKIA + 16 chars | AKIAIOSFODNN7EXAMPLE |
| GitHub PAT | ghp_ + 36+ chars | ghp_wWPw5k4aXcaT4fNP... |
| Stripe secret key | sk_live_ / sk_test_ + 24+ chars | sk_live_4eC39HqLyjWD... |
| Slack webhook | hooks.slack.com/services/... | full URL |
| Google API key | AIza + 35 chars | AIzaSyD-9tSrke72PouQ... |
| Private key block | -----BEGIN ... PRIVATE KEY----- | PEM header |
This is exactly why format-based regex scanning — the approach tools like Gitleaks, TruffleHog, and GitHub's own secret scanning all lead with — catches a large share of real leaks despite being, at its core, a list of pattern-matching rules. You don't need machine learning to notice a string starting with AKIA followed by 16 uppercase letters and digits; you need a rule that says exactly that.
One detail worth getting right if you build or evaluate a scanner yourself: Stripe's pk_live_/pk_test_ publishable keys are designed to be public — they're meant to sit in client-side JavaScript. Flagging them as a leaked secret is a straight-up false alarm. Only sk_ (secret) and rk_ (restricted) prefixed Stripe keys are actual credentials worth catching.
Where Regex Alone Falls Short
Format matching has a real ceiling. It can't tell you whether a matched AKIA... string is a live, currently-valid key or one that was rotated out six months ago — only that it looks like one. And it can't catch a credential in a format it has no rule for, which is why more thorough tools add two complementary techniques:
- Entropy analysis — flagging any string that's statistically "too random" to be a normal word or identifier, independent of matching a specific provider's format. Catches unknown/custom token formats, at the cost of more false positives (a long base64-encoded image or a hash can trip this too).
- Live validation — for providers that support it, actually calling the provider's API with the found credential (a benign request like
sts:GetCallerIdentityfor AWS) to confirm it's currently active. This turns "this looks like a secret" into "this secret is real and currently valid" — a much stronger signal, at the cost of needing live network access and raising its own handling-care questions (you're now making authenticated requests with a credential you just found).
ToolNinja's Secret Scanner uses the first approach — format-based rules for the most common providers, plus a lower-confidence check for secret-sounding variable names holding a literal string — entirely in your browser, with no network calls and no live validation. It's a fast first pass, not a replacement for a real scanning pipeline on your actual repository.
The Part That's Easy to Get Wrong: It's Already in History
Finding a hardcoded secret and deleting that line in a new commit feels like a fix. It isn't. Git keeps every version of every file in its history by default — the secret is still sitting in the commit where you first added it, retrievable by anyone with read access to the repository (or who finds an old clone, fork, or cached copy) using nothing more than git log -p or git show.
The only reliable response once a real secret has been committed:
- Rotate or revoke the credential at the provider immediately. This is the step that actually neutralizes the leak — everything else is cleanup.
- Remove it from your working code, moving it to an environment variable (a
.envfile excluded via.gitignore) or a proper secrets manager for anything beyond local dev. - Optionally scrub git history (
git filter-repo, BFG Repo-Cleaner) if the exposure is serious enough to warrant it — but understand this rewrites history and requires everyone with a clone to re-sync, so it's a bigger operation than step 1 or 2 and isn't always necessary once the credential itself is already dead.
Step 1 is the one that actually matters. A rotated key is safe to leave sitting in old git history, in the sense that it no longer grants access to anything — which is precisely why "just rotate it" is the standard first response from every major provider's own incident guidance.