Skip to main content
Back to blog
March 30, 2026·Updated May 2, 2026|10 min read|Antoine Duno|Web Security

How to Find and Remove Hardcoded API Keys from Your Codebase

Hardcoded API keys in source code are one of the most common and costly security mistakes in software development. This guide shows you how to find them with grep and automated tools, remove them from git history, and prevent them from appearing again.

Antoine Duno

1,600 words

AD

Antoine Duno

Founder of ZeriFlow · 10 years fullstack engineering · About the author

Key Takeaways

  • Hardcoded API keys in source code are one of the most common and costly security mistakes in software development. This guide shows you how to find them with grep and automated tools, remove them from git history, and prevent them from appearing again.
  • Includes copy-paste code examples and step-by-step instructions.
  • Free automated scan available to verify your implementation.

How to Find and Remove Hardcoded API Keys from Your Codebase

Finding hardcoded API keys in your codebase is not optional — it is a pre-release checklist item. GitHub''s own research found that hundreds of thousands of secrets are pushed to public repositories every year. The majority are API keys, database credentials, and OAuth tokens.

In many cases, automated bots scrape GitHub for secrets within minutes of a public push. By the time you notice the leak and rotate the key, it may already have been used.

This guide covers how to find secrets that are already in your code, remove them from git history permanently, and prevent them from entering your codebase again.


Why Hardcoded Secrets Are So Common

Developers hardcode secrets for understandable reasons: - It works immediately without any setup - Environment variables require more configuration - It is easy to forget a development key is still in the code - Copy-paste from documentation or Stack Overflow includes placeholder keys that get replaced with real ones

The result is that secrets end up in: - .env files committed to git (the most common) - Configuration files (config.json, settings.py) - Source code directly (strings in function calls) - Test files (especially integration tests against real APIs) - Comments (base64-encoded or "for reference only") - CI/CD configuration files


Method 1: grep with Regex Patterns

grep is available everywhere and requires no installation. These patterns catch the most common secret formats:

bash
# Generic high-entropy strings (catches many key formats)
grep -rE ''[A-Za-z0-9+/]{40,}'' --include="*.js" --include="*.ts" --include="*.py" \\
  --exclude-dir=node_modules --exclude-dir=.git .

# API key patterns
grep -rE ''(api[_-]?key|apikey|api_secret|access[_-]?token)["\\s:=]+["\\x27][A-Za-z0-9_\\-]{20,}'' \\
  --include="*.{js,ts,py,rb,go,env,json,yaml,yml}" \\
  --exclude-dir=node_modules --exclude-dir=.git .

# AWS keys
grep -rE ''AKIA[0-9A-Z]{16}'' --exclude-dir=node_modules --exclude-dir=.git .

# Stripe keys
grep -rE ''sk_(live|test)_[A-Za-z0-9]{24,}'' --exclude-dir=node_modules --exclude-dir=.git .

# SendGrid API keys
grep -rE ''SG\\.[A-Za-z0-9_-]{22}\\.[A-Za-z0-9_-]{43}'' --exclude-dir=node_modules .

# GitHub tokens (personal access tokens)
grep -rE ''ghp_[A-Za-z0-9]{36}'' --exclude-dir=node_modules --exclude-dir=.git .
grep -rE ''github[_-]?token["\\s:=]+["\\x27][A-Za-z0-9_]{40}'' \\
  --exclude-dir=node_modules --exclude-dir=.git .

# Google API keys
grep -rE ''AIza[0-9A-Za-z\\-_]{35}'' --exclude-dir=node_modules --exclude-dir=.git .

# Slack tokens
grep -rE ''xox[baprs]-[0-9A-Za-z]{10,}'' --exclude-dir=node_modules --exclude-dir=.git .

# Twilio
grep -rE ''AC[a-z0-9]{32}'' --exclude-dir=node_modules --exclude-dir=.git .

# Database connection strings with credentials
grep -rE ''(postgres|mysql|mongodb)://[^:]+:[^@]+@'' \\
  --exclude-dir=node_modules --exclude-dir=.git .

# Private key headers (SSH, TLS)
grep -rl ''BEGIN RSA PRIVATE KEY\\|BEGIN EC PRIVATE KEY\\|BEGIN OPENSSH PRIVATE KEY'' \\
  --exclude-dir=node_modules --exclude-dir=.git .

Save these as a shell script (check-secrets.sh) and run it before every commit.


Method 2: Search git History

The most important place to check is not the current working tree — it is your entire commit history. A secret removed from the current codebase might still be in a commit from six months ago.

bash
# Search all commits for a specific pattern
git log -p | grep -E ''AKIA[0-9A-Z]{16}''

# Search all commits for common secret keywords
git log -p --all | grep -iE ''(password|secret|api_key|token|private_key)\\s*=\\s*["\\x27][^"\\x27]{8,}''

# Show all commits that touched a specific file
git log --all --full-history -- "**/.env*"

# Show the content of a specific commit
git show <commit-hash>

# List all .env files ever committed
git log --all --name-only --format="" | sort -u | grep -E ''\\.env''

If you find a secret in git history, it must be treated as compromised and rotated immediately — even if you are about to remove it from history.


Method 3: truffleHog

truffleHog is purpose-built for secret detection and uses both regex and entropy analysis to find secrets with fewer false positives than raw grep.

bash
# Install
pip install trufflehog3

# Or use Docker (no installation)
docker run --rm -v "$PWD:/pwd" trufflesecurity/trufflehog:latest \\
  filesystem --directory=/pwd

# Scan a git repository including history
docker run --rm trufflesecurity/trufflehog:latest \\
  git file:///path/to/your/repo

# Scan a GitHub repository directly
docker run --rm trufflesecurity/trufflehog:latest \\
  github --repo https://github.com/yourorg/yourrepo

# Scan a specific git ref
trufflehog git file://. --since-commit HEAD~10 --only-verified

The --only-verified flag attempts to verify that detected secrets are actually valid by calling the relevant API. This dramatically reduces false positives.

truffleHog output

Found verified result 🐷🔑
Detector Type: Stripe
Decoder Type: PLAIN
Raw result: sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Commit: a3b4c5d6e7f8...
File: src/payment/stripe.js
Line: 42

Method 4: ZeriFlow Advanced Scan

ZeriFlow''s Advanced Scan accepts a GitHub repository URL or a ZIP upload of your codebase and checks for:

  • Hardcoded secrets using pattern matching and entropy analysis
  • CVEs in your dependencies
  • Insecure API patterns (open debug endpoints, missing authentication middleware)

This is the fastest way to get a comprehensive secret scan alongside a dependency and API pattern audit in a single report, without setting up CLI tools locally.


How to Remove Secrets from git History

Once you have found a secret in your git history, removing it from the working tree is not enough. The secret is still in every past commit. Anyone who clones your repository can find it.

The only correct response is: 1. Rotate the secret immediately (before cleaning history) 2. Clean the git history 3. Force-push the rewritten history

BFG is faster and simpler than git-filter-repo for the common case of removing specific strings or files.

bash
# Install BFG
brew install bfg   # macOS
# Or download from rtyley.github.io/bfg-repo-cleaner/

# Clone a fresh mirror of your repo
git clone --mirror git@github.com:yourorg/yourrepo.git yourrepo.git

# Create a file listing strings to delete
echo ''sk_live_actual_secret_value_here'' > secrets.txt
echo ''AKIA_actual_aws_key_here'' >> secrets.txt

# Run BFG — replaces all instances of the strings in all commits
bfg --replace-text secrets.txt yourrepo.git

# Navigate into the repo and run gc
cd yourrepo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive

# Force push the rewritten history
git push

Warning: Rewriting git history is destructive. Everyone who has cloned or forked the repository must re-clone after you force-push. Coordinate with your team before doing this on a shared repository.

Removing an entire file from history

bash
# Remove a specific file (.env, config/secrets.json) from all commits
bfg --delete-files .env yourrepo.git
bfg --delete-files "*.pem" yourrepo.git

Using git-filter-repo (alternative)

bash
pip install git-filter-repo

# Remove a file from all history
git filter-repo --path .env --invert-paths

# Replace a specific string across all history
git filter-repo --replace-text secrets.txt

Rotating Compromised Credentials

After any secret exposure, assume it has been used by an attacker. Rotation order:

Stripe

  1. 1Dashboard → Developers → API Keys → Roll key
  2. 2Update all production and staging environments
  3. 3Verify webhooks are using the new key

AWS

  1. 1IAM → Users → Security credentials → Deactivate old key → Create new key
  2. 2Check CloudTrail for any suspicious activity using the old key
  3. 3Update all services, update ~/.aws/credentials, rotate any infrastructure automation

GitHub Personal Access Token

  1. 1Settings → Developer Settings → Personal Access Tokens → Delete old token
  2. 2Generate new token with minimum required scopes
  3. 3Check for any unauthorized repository access or workflow triggers

Database credentials

  1. 1Update the database user password
  2. 2Update all connection strings in all environments
  3. 3Invalidate any existing connection pools (restart services)
  4. 4Check database audit logs for unauthorized queries

General checklist after key rotation

  • [ ] Old key is revoked or deleted (not just rotated)
  • [ ] New key is stored only in environment variables or a secrets manager
  • [ ] All environments (production, staging, CI/CD) are updated
  • [ ] Relevant audit logs have been reviewed for unauthorized use
  • [ ] The team has been notified

Preventing Future Secret Leaks

Pre-commit hooks with detect-secrets

bash
pip install detect-secrets

# Scan your current codebase and create a baseline
detect-secrets scan > .secrets.baseline

# Set up the pre-commit hook
cat > .git/hooks/pre-commit << ''EOF''
#!/bin/sh
detect-secrets-hook --baseline .secrets.baseline
EOF
chmod +x .git/hooks/pre-commit

GitHub secret scanning

Enable GitHub''s built-in secret scanning in your repository settings. It automatically scans pushes for over 200 secret patterns from major service providers and notifies you (and sometimes the service provider) immediately.

For public repositories, this is enabled automatically. For private repositories, it requires GitHub Advanced Security (included in GitHub Team and Enterprise plans).

Environment variable patterns

The correct pattern for every secret:

js
// .env file (never commit this)
STRIPE_SECRET_KEY=sk_live_...
DATABASE_URL=postgres://user:pass@host:5432/db

// In code
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

// Validate at startup — fail fast if env vars are missing
if (!process.env.STRIPE_SECRET_KEY) {
  throw new Error("STRIPE_SECRET_KEY environment variable is required");
}

Add .env to .gitignore immediately when starting any project:

bash
echo ".env" >> .gitignore
echo ".env.local" >> .gitignore
echo ".env.production" >> .gitignore
echo ".env.*.local" >> .gitignore

Use a .env.example file with placeholder values to communicate required environment variables without exposing real ones:

bash
# .env.example — commit this
STRIPE_SECRET_KEY=sk_live_your_stripe_key_here
DATABASE_URL=postgres://user:password@localhost:5432/mydb
SENDGRID_API_KEY=SG.your_sendgrid_key_here

Summary

Finding and removing hardcoded API keys requires searching both your current working tree (grep, truffleHog) and your entire git history (git log -p). Any secret found in git history must be rotated immediately before rewriting history with BFG Repo Cleaner. Prevent future leaks with pre-commit hooks (detect-secrets), GitHub secret scanning, and always using environment variables for sensitive values. For a comprehensive automated scan of your repository including dependency CVEs and insecure API patterns, run an advanced scan at zeriflow.com.

Ready to check your site?

Run a free security scan in 30 seconds.

Related articles

Keep reading