Exposed API Key on GitHub — Fix It Now | Data Hogo
Exposed API key on GitHub? Revoke the key first — every second counts. Then remove it from git history. Provider URLs and git filter-repo commands included.
Rod
Founder & Developer
Stop reading and revoke the key right now.
Open a new tab, go to your API provider's dashboard, and invalidate the exposed credential. The steps below will be here when you come back. But every second that key stays active is a second it can be used against you.
Done? Good. Here's what you're dealing with and how to finish the cleanup.
This is the right order for a leaked API key on GitHub: revoke first, clean history second, verify third, prevent fourth. Most guides get this wrong — they start with the git commands. If you clean the history before revoking the key, the secret is still live while you're doing git surgery. Revoke it first.
Step 1: Revoke the Key Right Now — Before Anything Else
If you haven't done this yet: go revoke the key. Here are the direct links for the most common providers.
| Service | Where to Revoke | Notes |
|---|---|---|
| OpenAI | platform.openai.com/api-keys | Click the key → Delete. Generate a new one immediately. |
| Stripe | dashboard.stripe.com/apikeys | Click Roll key next to the exposed secret. |
| AWS IAM | console.aws.amazon.com/iam → Users → Security credentials | Select the access key → Make Inactive, then Delete. |
| GitHub PAT | github.com/settings/tokens | Delete the token. Generate a new one with minimum required scopes. |
| Supabase | Project Settings → API | No self-service key rotation. Contact Supabase support to request service_role key rotation. The anon key can be rotated from the dashboard. |
| Anthropic | console.anthropic.com/settings/keys | Archive the key. Create a new one. |
| Resend | resend.com/api-keys | Delete the key. Create a replacement. |
| Vercel | Project Settings → Environment Variables | Remove the old value, add the rotated key. |
If your provider isn't listed: search "[service name] revoke API key" and do it now. Every major provider has this option.
What revoking does: It stops all future use of that credential instantly. Anyone who captured it — including automated scrapers — can no longer authenticate with it. Past activity is a separate question (covered below), but you've stopped the bleeding.
Why Deleting the File Won't Fix an Exposed API Key
Here's what a lot of developers don't realize when they push a secret by accident: deleting the file and pushing a new commit doesn't remove the secret from GitHub. Git stores every version of every file in every commit. That old commit with your .env file is still there. Anyone who can access the repo can run git checkout <old-commit-hash> -- .env and read it.
But there's a worse problem. Automated scrapers continuously monitor new public GitHub commits for credential patterns. GitGuardian's research on automated credential detection documents that secrets pushed to public repos are captured by automated tools within seconds of the push. By the time you noticed the mistake, your key may already have been collected by something you can't control.
GitHub's own secret scanning does the same thing — but so do tools operated by people who aren't trying to help you. Veracode's 2025 report found that 45% of AI-generated code contains at least one vulnerability — and hardcoded secrets are among the most consistent findings. If you used AI tools to generate your code, this is especially common. You're not alone in this situation.
This is also why revoking first matters: if the key was already scraped, cleaning the history doesn't help the scrapers who already have it. Revocation stops them.
Step 2: Remove the Secret From Your Git History
Now that the key is revoked, fix the history so the secret doesn't live there permanently.
First: is your repo public or private? If it was public at any point since the secret was committed, assume the secret was already seen. Clean the history anyway — you don't want it sitting there — but treat the key as compromised regardless.
Option A — git filter-repo (Recommended)
git filter-repo is the modern standard for rewriting git history. The older git filter-branch command is deprecated — don't use it. BFG Repo Cleaner is a solid alternative (covered below), but it requires Java and git filter-repo is faster for most cases.
Install:
# macOS
brew install git-filter-repo
# Python (any OS)
pip install git-filter-repoRemove an entire file from all history:
# This removes .env from every commit in the repo's history.
# --invert-paths means "keep everything EXCEPT this path"
git filter-repo --path .env --invert-pathsOr scrub a specific key value from all history:
# Replace the exact key string wherever it appears in history.
# Use your actual exposed key value in place of the example.
# sk-proj-xxxxxxxxxxxxxxxx is the fictional key pattern used here.
git filter-repo --replace-text <(echo 'sk-proj-xxxxxxxxxxxxxxxx==>REMOVED')The --replace-text approach is useful when the file itself should stay but the key value needs to be scrubbed. The --path --invert-paths approach is cleaner when you want the whole file gone.
Force-push to update the remote:
# Update all branches on the remote with the rewritten history
git push origin --force --all
# Update all tags too (tags reference commits, which may contain the secret)
git push origin --force --tagsIf you have collaborators: Anyone who cloned the repo before this push now has a local copy with the old history. They need to either re-clone the repo from scratch, or run
git fetch --all && git reset --hard origin/main. Let them know.
GitHub cache note: GitHub may keep cached views of old commit content briefly after a force-push. You can contact GitHub support to request expedited cache purging for a sensitive data removal. The GitHub documentation on removing sensitive data covers this process in detail.
Option B — BFG Repo Cleaner
BFG is a good option when git filter-repo isn't available or you're working with a very large repo.
# Clone a bare copy of your repo first — BFG requires this
git clone --mirror https://github.com/you/your-repo.git
# Run BFG to delete the .env file from all history
java -jar bfg.jar --delete-files .env your-repo.git
# Clean up and push
cd your-repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force --all
git push --force --tagsSee the BFG Repo Cleaner documentation for the full set of options, including --replace-text for scrubbing specific strings.
Option C — Delete and Re-Create the Repo
Sometimes this is the right call. If the repo is brand new, has no forks, no open issues you care about, and no collaborators, deleting and re-creating it is the fastest and cleanest recovery.
- Note any open PRs or issues you want to preserve (screenshots or export)
- Create a new local copy of your code with the secret removed from all files
- Delete the GitHub repo (Settings → Danger Zone → Delete this repository)
- Create a new repo with the same name
- Push your cleaned local copy as fresh history
What you lose: stars, forks, and all issue/PR history. If any of those matter to your project, use Option A instead.
Step 3: Verify Your Repo Is Clean
Manual history rewriting can miss things. Before you call this done, verify.
Check that the file is gone from all history:
# If this returns nothing, the file no longer exists anywhere in history
git log --all --full-history -- .envSearch for any remaining instances of the key value:
# Replace 'sk-proj' with the prefix pattern of your actual key type.
# If this returns nothing, no commit in history contains that string.
git log -p | grep "sk-proj"Check GitHub's secret scanning alerts:
Go to your repo on GitHub → Security tab → Secret scanning alerts. If GitHub flagged the key, verify the alert shows as resolved after your remediation. If it's still showing as active, the secret may still be present somewhere.
Scan with Data Hogo to confirm nothing was missed:
After manual cleanup, run an automated scan to confirm your repo is clean. Data Hogo's secrets engine uses Gitleaks plus custom patterns to check 250+ secret formats across your entire codebase — including formats that grep misses when the key is encoded, split across lines, or stored in an unexpected file type. We've scanned repos where the .env was removed from the latest commit but was still readable in commits from 6 months back, or where a key was duplicated into a config file that didn't get cleaned.
Check if your .env is still exposed → | Scan your full repo to confirm no secrets remain →
The free plan covers your first 3 scans. No credit card required.
Step 4: Make Sure It Can't Happen Again
This section is prevention, not lecture. Here's what to add to your workflow.
Add .env to .gitignore the right way
The standard entries for keeping secrets out of git:
# Environment variables — never commit these
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
.env.*
# But do commit the example file (with no real values)
!.env.exampleThe common trap: You added .env to .gitignore but the file was already committed in an earlier commit. Git still tracks it. .gitignore only prevents untracked files from being added — it doesn't stop tracking files that were already committed.
Fix it with:
# Remove .env from git tracking without deleting it from your local filesystem
# The --cached flag is critical — without it, git rm deletes the file
git rm --cached .env
git commit -m "chore: stop tracking .env file"After this, your .env still exists locally but git no longer watches it. Future changes won't get staged.
Use environment variables in your hosting platform
Don't put secrets in your code or in committed config files. Put them in your hosting platform's environment variable settings:
- Vercel: Project Settings → Environment Variables
- Railway: Project → Variables
- Netlify: Site Settings → Environment variables
Your code reads them as process.env.YOUR_KEY_NAME. Nothing sensitive ever touches the repo.
Enable GitHub push protection
Go to your repo → Settings → Code security → Push protection. Enable it.
Push protection intercepts commits that contain known secret patterns and blocks the push before the secret reaches the remote. It supports 200+ token formats. It won't catch every custom key format, but it catches the big ones: OpenAI, Stripe, AWS, GitHub PATs, and more.
One limitation: push protection only applies to future pushes. It doesn't scan your existing history.
Add a pre-commit hook with secretlint
For local catch-before-commit protection:
# Install secretlint in your project
npm install --save-dev @secretlint/secretlint secretlint-rule-preset-recommendThen add this to your package.json:
{
"scripts": {
"secretlint": "secretlint '**/*'"
},
"secretlint": {
"rules": [
{
"id": "@secretlint/secretlint-rule-preset-recommend"
}
]
}
}Wire it to your pre-commit hook via Husky or lint-staged to catch secrets before they're committed — not just before they're pushed. For a full picture of the security surface in a typical Next.js project, the Next.js security headers guide covers the HTTP layer and the Supabase RLS security checklist covers the database layer once your secrets are secured. You can check your headers for free and verify your .env isn't accessible in under a minute.
Data Hogo's secrets engine runs automatically every time you trigger a scan. It catches the same class of problems — exposed keys, committed .env files, hardcoded connection strings — and flags them as findings with severity labels. The free plan covers 3 scans per month.
Scan your repo free — catch exposed secrets before GitHub does →
What If My Key Was Already Used?
Revoking the key stops future use. But if the key was live for any window of time on a public repo, there's a chance it was used during that window. Here's how to check.
Check your API usage dashboard for unexpected spikes. Every major provider has usage metrics:
- OpenAI: platform.openai.com/usage — look for requests you didn't make
- Stripe: Dashboard → Logs — check for API calls from unfamiliar IP addresses
- AWS: CloudTrail logs (CloudWatch → CloudTrail) — look for
AssumeRole,CreateUser,RunInstances, andListBucketscalls you don't recognize
For AWS specifically: If you had an IAM key leaked, check CloudTrail for unauthorized API calls immediately. Attackers routinely use compromised AWS keys to spin up EC2 instances for crypto mining or to exfiltrate S3 data. If you see activity you don't recognize, contact AWS Support and their security team — they have a specific process for compromised credentials.
For Stripe: Check your payment history and webhook logs. A leaked Stripe secret key can be used to create refunds, modify customers, or access payout data. If you see unauthorized transactions, contact Stripe support immediately.
When to escalate: If you see unauthorized usage — unexpected charges, unfamiliar API calls, resources you didn't create — contact the provider's security team right away. Most major providers have a security disclosure or incident response process. Some (like AWS and Stripe) have teams specifically for compromised credential situations.
The honest truth: revoking the key stopped future damage. Past activity, if any occurred, is what to audit now. In most cases where a developer catches this quickly, there's no malicious usage — but the audit takes 5 minutes and gives you a clear answer.
Frequently Asked Questions
What should I do if I accidentally push an API key to GitHub?
Revoke or rotate the key immediately — before anything else. Go to your API provider's dashboard and invalidate the exposed credential. Then remove it from your git history using git filter-repo and force-push to update the remote. Finally, add the file to .gitignore, run git rm --cached to stop tracking it, and enable GitHub push protection to prevent recurrence.
How do I remove a secret from my GitHub commit history?
Use git filter-repo, the modern standard for rewriting git history. Install it with pip install git-filter-repo or brew install git-filter-repo. To remove a file from all history: git filter-repo --path .env --invert-paths. To scrub a specific key value: git filter-repo --replace-text <(echo 'YOUR_KEY==>REMOVED'). Then force-push with git push origin --force --all. Any collaborators will need to re-clone the repo.
Does revoking an API key make a leaked secret safe?
Revoking the key prevents all future use — that's the most important step and you should do it first. But it doesn't undo access that happened before revocation. If the key was active on a public repo, check your API usage dashboard for spikes or unauthorized activity. The key is no longer usable after revocation, but any past activity during the exposure window may have already occurred.
Can GitHub automatically detect exposed API keys?
Yes. GitHub's secret scanning detects over 200 token formats from major providers and alerts repository owners when a match is pushed. For public repos, it runs automatically at no cost. GitHub also offers push protection, which blocks the push before the secret reaches the remote. That said, GitHub's scanning only covers known patterns — custom API keys and connection strings with non-standard formats may not be detected.
How long does it take for a leaked AWS key to get exploited?
Minutes, sometimes seconds. Automated scrapers monitor new public GitHub commits for credential patterns continuously. GitGuardian's research documents that secrets pushed to public repos are detected by automated tools within seconds of the push. AWS keys are particularly targeted because they can provision expensive compute resources or access S3 buckets. Don't assume a quick delete was fast enough — the key may have already been captured.
Is deleting the commit enough to protect an exposed secret?
No. Deleting the file from the latest commit leaves every previous commit intact. Git stores the full history, and anyone with repo access can retrieve the file from an old commit. You need to rewrite history using git filter-repo or BFG Repo Cleaner to remove it from every commit. And even after rewriting history, revoke the key — automated scrapers may have already captured it from the original push.
You're going to be fine. This happens to developers at every level — AI-generated code already tends toward security mistakes, and secrets in committed files are among the most common findings across the industry. The steps above fix it completely.
To recap: revoke the key (done), clean the history (done), verify the repo is clean, prevent recurrence. You've handled the hard part.
Related Posts
OWASP A07 Authentication Failures Guide
OWASP A07 authentication failures with real code examples: weak passwords, JWT without expiry, localStorage tokens, no rate limiting, and how to fix each.
OWASP A01 Broken Access Control Guide
Broken access control is the #1 OWASP risk. This guide explains IDOR, missing auth checks, JWT tampering, and how to fix them with real Next.js code examples.
OWASP A02 Cryptographic Failures Guide
OWASP A02:2021 Cryptographic Failures is the #2 web vulnerability. Learn how plaintext passwords, weak hashing, and hardcoded keys expose your users — with real code examples.