Security Blog

Secret Scanning in Git Repositories: Stop Credential Leaks Before They Ship

AWS keys, database passwords, API tokens — developers accidentally commit them every day. Here's how secret scanning works and why you need it on every PR.

secret scanninggitleakscredentialsgithub

The most common cause of cloud account compromise isn't a sophisticated attack. It's a developer committing an API key to a public GitHub repository, or a private one that gets breached later. It happens dozens of times per day across the internet.

Why "I deleted the commit" doesn't work

Git is an append-only history. When you commit a secret, then "delete" it in the next commit, the secret still lives in your git history at the original commit hash. Anyone who has cloned the repo — or anyone who queries the git object store — can find it.

The only real fix is to revoke and rotate the credential. But if you don't know you committed it, you can't revoke it.

How secret scanning works

Tools like gitleaks scan your git history using pattern matching rules. Each rule is a regular expression targeting a specific credential format:

  • AWS access keys (AKIA[0-9A-Z]{16})
  • GitHub personal access tokens (ghp_[A-Za-z0-9_]{36})
  • Stripe live API keys (sk_live_[0-9a-zA-Z]{24})
  • Google Cloud service account keys (JSON structure pattern)
  • Database connection strings with embedded passwords
  • SSH private keys
  • JWT secrets and HMAC signing keys

Modern rulesets cover hundreds of credential types across major cloud providers, SaaS tools, and common patterns.

PR-time vs scheduled scanning

PR-time scanning

The most important check. When a developer opens a PR, scan the commits in that branch for any newly introduced secrets. This catches the credential before it merges to main and before it's deployed — the moment intervention is cheapest.

BattleTest runs secret scanning against every PR diff automatically.

Scheduled scanning

Run against the full repository history periodically to catch things that slipped through historical commits or were committed before automated scanning existed. Lower urgency — these secrets may already be compromised — but important for baseline cleanup.

Common false positives and how to handle them

Secret scanning produces false positives. A test file with a placeholder that looks like an API key. A documentation example showing the format of a credential. A hardcoded hash that triggers the "SHA-256 key" pattern.

Good tools handle this with:

  • Entropy analysis: Real secrets have high entropy. "example-key-here" doesn't.
  • Context analysis: "this is a test" in a comment near the match reduces confidence.
  • Allowlists: .gitleaks.toml with specific paths or patterns to ignore.
  • Verification: For some credential types, you can actually call the API with the found key to verify it's valid before alerting.

What to do when you find a leaked secret

  1. Revoke immediately — don't wait, assume it's already been found by scanners that watch GitHub
  2. Rotate to a new credential — generate a new key, update your production config
  3. Check access logs — has the credential been used by anyone other than your application?
  4. Remove from history — use git filter-branch or BFG Repo Cleaner to scrub the commit, then force-push (accept that anyone who cloned before is not covered)
  5. Add to allowlist or fix the code — if it was a false positive or a legitimately-formatted test value, document it

Preventing secrets in the first place

  • Use a .env file for local development secrets and add .env* to .gitignore
  • Use environment variables in CI/CD, not committed config files
  • Use a secrets manager (AWS Secrets Manager, HashiCorp Vault, Doppler) for production
  • Add a pre-commit hook that runs gitleaks locally before pushing
  • Run automated secret scanning on every PR

The last step is the safety net. The others are good hygiene. Both are necessary.

Try BattleTest

Catch this class of bug before it ships.

BattleTest reviews every PR for injection flaws, leaked secrets, and CVEs — then battle-tests your live infrastructure the way an attacker would.

Add secret scanning to your repos free →

Free for public repos · Private repos from $20/mo · No credit card required