Skip to content

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.

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:

  1. Find all occurrences: grep -r "AKIAIOSFODNN7EXAMPLE" . (use your actual key prefix)
  2. Replace with an environment variable reference: process.env.AWS_ACCESS_KEY_ID
  3. Add the secret to your secrets manager or .env file (not committed)
  4. Add .env to .gitignore if 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)

Terminal window
# Install
pip install git-filter-repo
# Remove the file that contained the secret
git filter-repo --path path/to/secret/file --invert-paths
# Or replace the secret string everywhere in history
git filter-repo --replace-text <(echo 'ACTUAL_SECRET_VALUE==>REDACTED')

Option B: BFG Repo Cleaner (faster for large repos)

Terminal window
# Download BFG from https://rtyley.github.io/bfg-repo-cleaner/
# Create a file listing secrets to remove
echo 'AKIAIOSFODNN7EXAMPLE' > secrets.txt
# Run BFG
java -jar bfg.jar --replace-text secrets.txt your-repo.git
# Clean up
cd your-repo
git reflog expire --expire=now --all && git gc --prune=now --aggressive

Both tools rewrite every commit in your history that contained the secret.

After rewriting history locally, push the rewritten history to the remote:

Terminal window
git push --force --all
git push --force --tags

If 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.

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-secrets or gitleaks can 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.