How to Detect Secrets in Config Files Before They Leak
· 4 min read
To detect secrets in config files, scan them with a tool that combines known key patterns (such as AKIA for AWS access key IDs or ghp_ for GitHub tokens) with suspicious key names and high-entropy strings, and run it in three places: before you share a file, as a pre-commit hook, and in CI across the full Git history. For a JSON or config snippet you are about to paste somewhere, the JSON secret scanner checks it in your browser.
Where secrets hide in configs
.envfiles committed by accident, or copied into Docker images.appsettings.json,config.json,firebase.jsonand service-account key files (Google service-account JSON contains a full private key).- Connection strings with inline passwords:
postgres://app:hunter2@db:5432/prod. - Terraform state, Kubernetes manifests and Helm values (Kubernetes
Secretdata is base64, which is encoding, not encryption). - CI configuration, Postman collections and HAR files, which often include
Authorizationheaders. - Logs and API responses pasted into tickets, chats and online tools.
How scanners find secrets
| Technique | Example | Strength | Weakness |
|---|---|---|---|
| Provider patterns | AKIA[0-9A-Z]{16}, ghp_, sk_live_, xox[bp]-, -----BEGIN PRIVATE KEY----- | Precise, names the provider | Misses custom or unknown formats |
| Key names | password, secret, api_key, token, client_secret | Catches generic credentials | False positives such as "password": "" |
| Entropy | Long random-looking strings | Finds unknown key types | Noisy on hashes, IDs, base64 data |
| Verification | Test the key against the provider API | Confirms it is live | Sends the secret over the network |
Good scanners combine the first three and let you tune the rest with allowlists. Verification is useful in CI but should be a deliberate choice, because it means your secret is sent to a third party for checking.
A worked example
{
"env": "production",
"aws": { "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "region": "eu-west-1" },
"db": { "url": "postgres://app:s3cr3t-pass@db.internal:5432/app" },
"stripe": { "key": "sk_live_REPLACE_ME_0000000000" },
"featureFlags": { "newCheckout": true },
"buildHash": "9f86d081884c7d659a2feaa0c55ad015"
}A scanner should flag the AWS key ID (provider pattern), the database URL (credentials inside a connection string) and the Stripe key (the sk_live_ prefix). The buildHash is random-looking but harmless; it is the classic entropy false positive, which is why key names and context matter. The AWS value shown is the documentation example key, safe to publish.
Tools for each stage
| Stage | Tool | Command or setup |
|---|---|---|
| Before sharing a snippet | JSON secret scanner | Paste, review findings, redact, copy |
| Pre-commit | gitleaks | gitleaks protect --staged (or the gitleaks git --pre-commit --staged form in newer releases) |
| Pre-commit | detect-secrets (Yelp) | detect-secrets scan > .secrets.baseline, then the pre-commit hook |
| Full history / CI | trufflehog | trufflehog git file://. --only-verified |
| Full history / CI | gitleaks | gitleaks detect (or gitleaks git in newer releases) |
| Hosted repos | GitHub secret scanning and push protection | Enable in repository security settings |
Command syntax changes between versions, so check --help for the release you install. The important design point is layering: the pre-commit hook stops most leaks on the developer's machine, CI catches anything that bypassed the hook, and history scans find what was committed before you started.
Setting up a pre-commit hook
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0 # pin to the release you have reviewed
hooks:
- id: gitleakspip install pre-commit
pre-commit install # run on every commit
pre-commit run --all-files # scan everything onceKeep a reviewed allowlist (gitleaks uses .gitleaksignore and config allowlists, detect-secrets uses a baseline file) so test fixtures do not train the team to ignore warnings.
What to do when a scanner finds a real secret
- Revoke or rotate it first. Assume it is compromised the moment it was pushed or pasted anywhere public. Deleting the file does not help: Git history, forks, caches and tool logs keep copies.
- Check for use. Review the provider's access logs (for example AWS CloudTrail) for activity you do not recognize.
- Remove it from history if the repository is shared, with
git filter-repoor BFG, then force-push and ask collaborators to re-clone. This is cleanup, not a fix; rotation is the fix. - Move it to a secret store: environment variables injected at deploy time, a cloud secret manager, or your CI's encrypted secrets. Commit a
.env.examplewith placeholders and add.envto.gitignore. - Add the pattern to your scanner config if it was a format the tools missed.
Sharing configs safely
Much of the real-world exposure comes from sharing, not committing: a config pasted into a public formatter, a log attached to a ticket. Scan and redact before you share. The JSON secret scanner and the Redact option in the JSON formatter replace detected values while keeping the JSON valid, and neither sends your data to a server. For the background, read how to safely format JSON without leaking API keys and the JSONFormatter and CodeBeautify leak explained.
FAQ
How do I find API keys in my code or config files?
Run a secret scanner such as gitleaks, trufflehog or detect-secrets over the files and Git history. For a single JSON snippet, paste it into a browser-based secret scanner.
Is base64 a safe way to store secrets in config?
No. Base64 is reversible encoding. Anyone with the file can decode it, and good scanners decode base64 to look inside.
I committed a secret and deleted it. Am I safe?
No. The value remains in Git history and anywhere it was cloned or cached. Rotate the secret, then clean history if needed.
Why does the scanner flag things that are not secrets?
Entropy checks flag any long random-looking string, such as hashes and IDs. Add reviewed allowlist entries rather than disabling the scanner.
Should secrets live in .env files?
.env files are fine for local development if they are gitignored. In production, inject secrets from a secret manager or your platform's environment settings.

