Secrets in source: the leak that keeps on giving
A secret in source code is a gift that keeps giving — to whoever finds it. We find them constantly: in mobile app binaries, in repositories, in front-end bundles, in config files shipped to customers. Each one is a working credential handed directly to an attacker, no exploitation required.
Where secrets hide
- Mobile binaries. The app is a file on the attacker's device. Unzip it, read the strings, and the API keys and backend credentials compiled into it are right there.
- Front-end code. Anything in client-side JavaScript is public. An "internal" API key in a bundle is not internal.
- Repositories and history. A secret committed and later deleted still lives in git history forever. Public repos and leaked internal ones are mined constantly.
- Config shipped to clients. Default credentials, signing keys, connection strings bundled into installers.
Why it keeps happening
Secrets get hardcoded because it's the path of least resistance under deadline, and because "nobody will look." People look — automatically, at scale. The deeper problem is architectural: the application was designed to hold a long-lived secret at all. If a value in your shipped artifact would be dangerous in an attacker's hands, it shouldn't be in the artifact.
How to stop leaking
- Don't ship long-lived secrets. Authenticate the user and broker access to back-ends server-side; scope every token to that user and session.
- Secrets managers, not config files. Inject secrets at runtime from a vault/secrets manager; keep them out of the repo and the build.
- Scan continuously. Pre-commit hooks and CI secret-scanning catch leaks before they ship. Scan history, not just the current tree.
- Rotate on exposure. A leaked secret is compromised the moment it's committed. Make rotation a one-step operation so it actually happens.
Obfuscation buys minutes. Architecture buys safety: the only secret an attacker can't steal from your artifact is the one that was never in it.
Rotation is the part everyone skips
Finding a secret is the easy half. The hard truth is that a secret is compromised the instant it’s committed — deleting it seconds later doesn’t help, because version-control history keeps it and automated miners move fast. The only safe response to an exposed secret is to rotate it. Teams skip this because rotation is painful: a key wired into ten services by hand is a multi-day project nobody wants to start. Make rotation a one-step, routine operation before you have an incident, so that when a secret leaks, replacing it is minutes of work rather than a reason to hope nobody noticed.
The dependency angle
Your secrets aren’t only in your code. Every dependency you pull in runs in the same process and can read the same environment — so a compromised or malicious package can lift the credentials your app holds. Scope secrets as tightly as the task allows, prefer short-lived tokens, and assume third-party code can see whatever the process can see. Supply-chain risk and secret hygiene are the same conversation.
Under the bonnet: how secret scanning actually works
Scanners lean on two complementary techniques, and understanding both explains why they miss things. The first is pattern matching: regular expressions tuned to the shape of known credentials, often anchored to a provider-specific prefix and a fixed length. These are precise and cheap, but they only catch secrets whose format someone has already written a rule for. A bespoke HMAC signing key or an internal token with no distinctive prefix sails straight through.
The second is entropy analysis. Random, high-information strings score higher on Shannon entropy than ordinary prose or code identifiers, so a scanner flags long base64 or hexadecimal blobs that look "too random" to be a variable name. This generalises to unknown secret formats, but it is noisy: UUIDs, content hashes, minified JavaScript, test fixtures and lockfile integrity digests all look like high-entropy junk and drive false positives. Tune the threshold down to silence them and you create false negatives — a short API key or a low-entropy password quietly slips under the bar.
The honest takeaway is that no single pass is complete. Pattern rules miss the unusual; entropy misses the mundane. The strongest tools pair both with validation — calling the provider to confirm a candidate is live — which cuts noise dramatically but should only ever be done against your own tenancy.
How to do it right
- Run pattern and entropy detection together, and treat validation-confirmed hits as priority-one.
- Maintain an allowlist of known-benign patterns (hashes, fixtures) rather than lowering global entropy thresholds.
- Scan the full commit history on first adoption, then scan incrementally on every change.
Deleting isn't purging: cleaning git history properly
A secret committed to a repository is not removed by deleting the file and committing again. Git is content-addressable and append-only: the old blob still lives in history, reachable through the previous commit, and anyone who has cloned or forked the repository still holds a complete copy. Mirrors, CI caches, fork networks and pull-request refs all retain it too.
Properly excising a secret means rewriting history with a tool built for the job, force-pushing the rewritten branches, and then expiring reflogs and running garbage collection so the unreferenced blobs are actually dropped. On a hosted platform you must also ask support to purge cached views and detached pull-request commits, and coordinate every collaborator to re-clone, because a stale local clone can reintroduce the old objects on the next push.
All of this is slow, disruptive and never fully guaranteed once a repository has been shared. That is why history rewriting is damage limitation, not a fix.
How to do it right
- Rotate first. Assume the secret is compromised the moment it was pushed; revoke and reissue it before touching history.
- Rewrite history to shrink the exposure window, then expire reflogs and force garbage collection.
- Request platform-side cache and fork-ref purging, and have all clones re-created from the rewritten remote.
Removing the target: short-lived credentials and workload identity
The most durable defence is to have no long-lived secret to leak. Workload identity federation lets a pipeline or workload present a signed OIDC token describing what it is to a cloud provider, which exchanges it for a credential valid for minutes. Nothing static is stored; a leaked token has usually already expired, and trust is scoped to a specific repository, branch or workload rather than a bearer string anyone can replay.
This collides with the "secret zero" problem: a workload still needs some initial trust anchor to prove its identity. The goal is not to eliminate that anchor but to reduce it to a single, tightly-scoped, platform-attested identity — and to anchor it in something the environment inherently possesses rather than a copied string.
How to do it right
- Replace static cloud keys in CI with OIDC federation scoped to specific repositories and branches.
- Issue database and service credentials dynamically with short leases, so exposure is self-limiting.
- Anchor "secret zero" in platform-attested identity (instance identity documents, hardware roots) rather than a stored key.
Turning the tables: honeytokens as a tripwire
Prevention is never perfect, so instrument for detection. A honeytoken is a deliberately planted, realistic-looking but entirely inert credential — a fake API key in a config file, a decoy database string — that no legitimate process ever uses. Any attempt to authenticate with it can only mean someone has read where it was hidden, giving you a high-fidelity, near-zero-false-positive alert that your source, binary or config has been harvested.
How to do it right
- Seed honeytokens in the exact places real secrets leak from: repos, mobile binaries, front-end bundles and shipped config.
- Wire their use straight to alerting, capturing source address and timestamp for triage.
- Keep a register of where tokens are planted so a trigger is actionable, and rotate them like any other credential.
What's hiding in your binaries and repos?
Our secure code review and mobile testing find hardcoded secrets before attackers do — and help you design them out.
Talk to an engineer