API Key Leaked on GitHub: Immediate Steps and Prevention
Accidentally pushed an API key to GitHub? Learn what to do immediately, how to prevent future leaks, and what tools can automatically scan your repos for secrets.
It happens to almost every developer at least once: you accidentally commit an API key, database password, or private token to a public GitHub repository. Within minutes, automated bots are scanning GitHub for exactly this — and they will find it.
This guide walks through what to do immediately, how to prevent future leaks, and what tools to put in place so it never happens again.
Why This Is Urgent
Automated scanners continuously monitor GitHub's public event stream. Projects like truffleHog, GitGuardian, and even AWS itself monitor for leaked credentials. The window between a push and a credential being compromised can be measured in minutes.
In one well-documented incident, a developer committed an AWS key to GitHub and received an email from AWS about suspicious charges for EC2 instances in multiple regions within 4 minutes of the push.
Immediate Response: What to Do Right Now
Step 1: Revoke the key immediately
Before anything else, revoke or rotate the compromised credential:
- AWS: Go to IAM → Users → Security Credentials → Deactivate the access key
- Stripe: Dashboard → Developers → API Keys → Roll key
- SendGrid: Settings → API Keys → Delete
- GitHub PAT: Settings → Developer Settings → Personal Access Tokens → Revoke
- Google Cloud: Console → IAM → Service Accounts → Delete the key
Do not remove the commit first. Removing it from GitHub does not remove it from history, and it doesn't prevent automated scanners that already found it from using it. Revoke first.
Step 2: Check for unauthorized usage
After revoking, audit what was done with the key:
- AWS CloudTrail logs for suspicious API calls
- Your service's audit log for unusual access patterns
- Check for new resources (EC2 instances, S3 buckets, database users) you didn't create
Step 3: Remove from Git history
Even after revoking, removing the secret from history is good practice to prevent confusion. Use git-filter-repo (the modern replacement for git filter-branch):
pip install git-filter-repo
# Remove a file containing the secret from all history
git filter-repo --path path/to/secrets-file.env --invert-paths
# Or replace the specific string everywhere in history
git filter-repo --replace-text <(echo 'sk_live_abc123==>REVOKED_KEY')
Then force-push all branches:
git push --force --all
git push --force --tags
Note: GitHub caches repository data and it may take time to propagate. Contact GitHub support if the commit still appears in search results.
How Secrets Get into Repositories
Understanding the failure modes helps prevent them:
- Hardcoded in source files —
const API_KEY = "sk_live_..."directly in code - Committed
.envfiles —.envnot in.gitignore - Configuration files —
config.json,settings.py,application.properties - Test files — Secrets in test fixtures, mocks, or seed data
- Shell history —
.bash_historyor.zsh_historyaccidentally committed - IDE config files — Some editors store project settings with credentials
- Docker images — Build args baked into layers
Prevention: Environment Variables
The correct pattern is to never put secrets in source files. Use environment variables:
// ❌ Wrong
const stripe = new Stripe('sk_live_abc123');
// ✅ Correct
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
For local development, use a .env file — but always add it to .gitignore:
# .gitignore
.env
.env.local
.env.*.local
*.env
For production, use your platform's secrets management:
- Vercel: Environment Variables in project settings
- AWS: Secrets Manager or Parameter Store
- GitHub Actions: Repository Secrets
- Kubernetes: Secrets or external secret operators (External Secrets Operator)
Prevention: Pre-commit Hooks with gitleaks
Install gitleaks to scan every commit before it's pushed:
# Install gitleaks
brew install gitleaks # macOS
# or download binary from GitHub releases
# Scan the current repo for any historical leaks
gitleaks detect --source . --verbose
# Install as a pre-commit hook
gitleaks protect --staged # scan staged files before commit
Using the pre-commit framework:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
GitHub's Built-in Secret Scanning
GitHub automatically scans public repositories for secrets from 100+ service providers (AWS, Stripe, GitHub tokens, Twilio, etc.) and notifies the service provider when a match is found.
For private repositories, Secret Scanning is available on GitHub Advanced Security. You can also enable Push Protection, which blocks pushes that contain detected secrets before they reach the repository.
To check if secret scanning is enabled:
- Go to your repository → Settings → Security & Analysis
- Enable "Secret scanning" and "Push protection"
Scanning Your Entire Org History
If you're doing a retrospective audit:
# Scan all branches of a repo
gitleaks detect --source . --verbose --log-opts="--all"
# With truffleHog (also checks for high-entropy strings)
trufflehog git file://. --only-verified
# Scan a GitHub org (requires GitHub token)
trufflehog github --org=your-org --only-verified
Building a Secrets Management Policy
Beyond tooling, establish a team policy:
- No secrets in code, ever — use environment variables or secret stores
- Rotate keys regularly — at least annually, or immediately on team member offboarding
- Use short-lived credentials — IAM roles with temporary credentials instead of long-lived keys
- Principle of least privilege — each API key should only have the permissions it needs
- Audit regularly — scan your repos quarterly even if you have pre-commit hooks
The fundamental rule: treat secrets like passwords. You wouldn't commit a password to source control. Apply the same standard to every API key, token, and credential.