Secret scans are cheap compared to rotating keys.
The scanner is not the expensive part. The expensive part is the weekend that starts after a real credential has already landed in git history.
Once a key is in the repo, the work balloons: revoke it, replace it, check who used it, search logs, update consumers, maybe rewrite history, maybe invalidate caches, and explain what happened. A pre-commit or CI scan is boring by comparison.
the leak usually looks normal
Most leaks are not dramatic. They come from normal work:
- copying
.envinto an issue - committing a test fixture
- pasting a token into docs
- saving a generated config
- adding a screenshot with a visible key
- checking in a service account file
- logging headers while debugging
- dropping real credentials into a curl example
That is why scanning belongs near the path where the secret can enter: local commit, pull request, generated file, docs snippet, CI artifact, or test data.
the response should be structured
Scanners will flag things that are not secrets. That is fine. The important part is deciding what the finding actually is.
For each one, I want a quick classification:
- real secret
- test credential
- public example
- intentionally committed fixture
- generated false positive
- needs investigation
Real secrets get rotated. Test values should look obviously fake. Public examples should not resemble live credentials. Intentional fixtures should be documented. A scanner full of ignored false positives becomes background noise.
examples should not train the wrong habit
Docs and snippets often need values that look like secrets.
Bad:
OPENAI_API_KEY=sk-abc123
AWS_SECRET_ACCESS_KEY=abc123
Better:
OPENAI_API_KEY=replace-with-your-openai-api-key
AWS_SECRET_ACCESS_KEY=replace-with-your-aws-secret
Even better, use formats that are obviously impossible when the tool allows it. Realistic fake keys cause scanner noise and reader confusion.
generated files are a common leak path
Builds, SDKs, notebook outputs, and debug scripts write files that were never meant to be public.
That means I want generated output either ignored, cleaned, or scanned. A few ignore rules help, but they are not the whole control:
.env
.env.*
!.env.example
*.pem
*.key
service-account*.json
.local/
tmp/
Ignore rules reduce accidental staging. Scanning catches the things they miss.
ai tools make this more important
Agents can move text around very quickly.
They can read a file, generate a config example, update docs, or copy an error message into a fixture without understanding that the nearby string is sensitive. They also tend to produce realistic-looking placeholders unless the prompt and the repo conventions push them away from that shape.
That means the boring controls matter more, not less:
- keep real secrets out of repo-readable files
- scan diffs before commit
- scan generated docs and fixtures
- use fake example values
- review agent-created config changes carefully
the runbook should be short
When a scanner fires, the team should not improvise.
The runbook can be small:
- stop the merge
- identify the credential type
- notify the owner
- revoke or rotate if real
- remove the value from the diff
- check whether it reached remote history
- add a fixture or ignore rule only if it was a false positive
The important part is speed. A real secret should move from detection to revocation quickly.
Scanning will miss some things and flag things that are harmless. That does not make it optional. It just means the tool is part of a broader habit.
Security usually rewards the unglamorous control that prevents a boring mistake from becoming an incident. Secret scanning is one of those controls. It changes the economics enough that the scan pays for itself the first time it keeps a real key out of history.
Related posts

About Jeremy London
Engineering leader and builder in Denver. I write about AI platforms, agents, security, reliability, homelab infrastructure, and the parts of engineering work that have to survive production.