How to respond to an exposed credential
This guide covers what to do when BattleTest flags a committed secret, or when you discover one yourself. The steps apply to any credential: AWS access keys, API tokens, database passwords, private keys, or service account credentials.
Step 1: Rotate the credential immediately
Section titled “Step 1: Rotate the credential immediately”Before anything else, revoke the exposed credential and issue a new one. Do this now, before touching git history. If the key has been in a public or shared repository, assume it has already been read.
| Credential type | Where to rotate |
|---|---|
| AWS access key | IAM → Users → Security credentials → Make inactive → Create new |
| GitHub personal access token | Settings → Developer settings → Personal access tokens → Revoke |
| Stripe API key | Dashboard → Developers → API keys → Roll secret key |
| SendGrid API key | Settings → API Keys → Revoke |
| Database password | Your database admin panel or ALTER USER command |
| Other SaaS API key | Check the service’s security or developer settings |
After rotation, store the new credential in your deployment secrets manager (environment variables, Vault, AWS Secrets Manager, etc.). Do not put it in code.
Step 2: Remove the credential from current code
Section titled “Step 2: Remove the credential from current code”If the credential is still in your codebase (not just in history), remove it:
- Find all occurrences:
grep -r "AKIAIOSFODNN7EXAMPLE" .(use your actual key prefix) - Replace with an environment variable reference:
process.env.AWS_ACCESS_KEY_ID - Add the secret to your secrets manager or
.envfile (not committed) - Add
.envto.gitignoreif it isn’t already
Commit the removal.
Step 3: Purge the credential from git history
Section titled “Step 3: Purge the credential from git history”Removing a secret from current code doesn’t remove it from git history. You need to rewrite history.
Option A: git filter-repo (recommended)
# Installpip install git-filter-repo
# Remove the file that contained the secretgit filter-repo --path path/to/secret/file --invert-paths
# Or replace the secret string everywhere in historygit filter-repo --replace-text <(echo 'ACTUAL_SECRET_VALUE==>REDACTED')Option B: BFG Repo Cleaner (faster for large repos)
# Download BFG from https://rtyley.github.io/bfg-repo-cleaner/
# Create a file listing secrets to removeecho 'AKIAIOSFODNN7EXAMPLE' > secrets.txt
# Run BFGjava -jar bfg.jar --replace-text secrets.txt your-repo.git
# Clean upcd your-repogit reflog expire --expire=now --all && git gc --prune=now --aggressiveBoth tools rewrite every commit in your history that contained the secret.
Step 4: Force-push to the remote
Section titled “Step 4: Force-push to the remote”After rewriting history locally, push the rewritten history to the remote:
git push --force --allgit push --force --tagsIf this is an organisation repository with branch protection rules preventing force-push, you’ll need to temporarily disable protection rules or coordinate with an admin.
Step 5: Notify anyone who has access to the repository
Section titled “Step 5: Notify anyone who has access to the repository”If the repository is or was public, or if it’s a shared private repository, assume the credential has been seen. Notify your team that they need to re-clone and that the old credential is revoked.
If the key had write or admin access to a production system, check your audit logs for unexpected activity during the period the key was exposed.
Step 6: Prevent recurrence
Section titled “Step 6: Prevent recurrence”BattleTest scans every PR for secrets automatically, so a credential that lands in a pull request is flagged before it merges.
To avoid future incidents:
- Use environment variables. Never hardcode credentials, even in config files committed to a private repo.
- Add pre-commit hooks. Tools like
detect-secretsorgitleakscan prevent secrets from being committed in the first place. - Connect BattleTest to your repositories. Every PR gets scanned automatically. See Get your first PR security review.