Secrets Management
The Five Places API Keys Leak Most Often
Most leaked credentials show up in the same handful of files. Here is where to look first.
5 min.
Almost every credential we find in a repository was committed by a competent developer having an ordinary day. Nobody sets out to publish an AWS key. Someone needs a value to make a local run work, pastes it into the nearest file, and ships the feature it was blocking. The leak is a side effect of momentum, not carelessness.
That is good news, because ordinary mistakes repeat in predictable places. Across the repositories KAIKI has scanned, the same five locations account for the overwhelming majority of real findings. If you only have twenty minutes to audit a codebase, spend them here.
1. Config files that were never meant to ship
config.js, settings.py, application.yml, appsettings.json. A developer hardcodes a key to unblock a local run, intends to move it to an environment variable later, and then the file is committed with the rest of the branch.
These are the highest-severity findings we see, for two reasons. The value is almost always live, because the application reads it at startup and would break if it were not. And the file is imported by production code, so the credential usually carries real permissions rather than sandbox scope.
How to check quickly
Read every file your application loads during boot, then follow the imports one level out. Anything that resolves to a literal string where you expected a lookup deserves attention, especially values added in a hurry near a release.
2. .env files added before .gitignore
Adding .env to .gitignore does nothing for a file Git already tracks. The ignore rule only applies to untracked paths, so a file committed on day two keeps being committed for the next two years, and the rule creates a false sense of safety.
Deleting the file later does not help either. Every earlier commit still contains it, and anyone who can clone the repository can read it. If the project was ever public, ever forked, or ever cloned by a contractor, treat those values as compromised.
3. Test fixtures and seed data
Test files are where scanners generate the most noise and where real keys occasionally hide. A fixture starts life as an obvious fake, then someone needs a passing integration test against a live sandbox and swaps in a working token. The commit message says something like fix flaky test.
The tell is a value that looks structurally valid rather than obviously invented. Provider prefixes, correct lengths, and checksum-clean strings inside a test directory are worth a manual read even when the surrounding code looks harmless.
4. CI configuration and build scripts
Workflow files, Dockerfiles, and deploy scripts get edited under pressure, usually while a pipeline is red. Inlining a value is faster than adding a repository secret and wiring it through, and it is exactly as public as any other committed line.
Docker makes this worse. ENV and ARG values are baked into image layers and stay readable in the image history even if a later step unsets them. A key that existed for one build step still ships inside the artifact.
5. Notebooks, scratch files, and READMEs
Jupyter notebooks store output as well as source, so a printed response or a debug cell can carry a token that never appears in the code itself. Setup instructions in a README get written with a working example, because that is what the author had open. Scratch files named test2.py or temp.sh survive far longer than anyone intends.
The riskiest file in a repository is rarely the one under review. It is the one nobody remembers writing.
A triage order that works
When a scan comes back with findings, resist the urge to start rewriting history. Rotation is the only step that ends the exposure; everything else is housekeeping that can happen afterwards.
Rotate anything found in a config file, a .env, or an infrastructure file, in that order.
Check the provider audit log for the exposure window before you close the ticket.
Untrack the file so the next commit does not repeat the leak.
Scan the full history, not only the current tree, because deleted files still resolve.
Rewrite history last, once the credentials it contains are already dead.
Making the next one unlikely
Documentation does not prevent this pattern and neither does good intent, because the mistake happens in the ten seconds when someone is thinking about something else. What works is a check that runs without being asked: a scan on every push, a fast pre-commit hook, and one obvious place where secrets are supposed to live, so that anything outside it looks wrong.
The goal is not a team that never pastes a key into a file. It is a team that finds out in minutes instead of eight months.
KAIKI scans source, config, infrastructure, and notebook files in a single pass and rates every finding by severity, so a live config-file credential does not get buried under fifty test fixtures.
Ship With Confidence
Zero config, 50+ secret patterns, AI-powered analysis. Start scanning in seconds.