Take a breath. This is one of the most common mistakes in software, it is recoverable, and the recovery has a strict order: rotate the key first, clean up git second. History with a dead key in it is an archive. History with a live key in it is a countdown. Everything below is in the order you should do it, so just work top to bottom.
Step 1: Rotate the key. Now, before git.
Log in to the provider the key belongs to, generate a replacement, revoke the exposed one, and put the new key in your deployed environment variables. Not in the code. The general shape is the same everywhere: find the API keys page in the provider's dashboard, create the new key, delete or roll the old one, update your app's environment, redeploy.
- Stripe: the developer dashboard has a roll action for exactly this situation.
- OpenAI and similar AI providers: delete the exposed key on the API keys page and create a fresh one.
- Supabase: rotate from your project's API settings; if it was the service_role key, do this first of everything, since that key bypasses your database's row-level protections entirely.
- AWS: deactivate the exposed access key, create a new one, and check for resources you did not create before deleting anything.
If the app breaks for a few minutes while you redeploy, that is fine. A short outage you caused is better than a long one somebody else causes.
Two special cases worth naming. If the key was a cloud account credential, AWS or similar, treat this step with extra respect: those keys can create resources that bill by the hour, so after deactivating, look through the account for instances, users, or access keys you did not make. And if the key belonged to someone else, a client's Stripe account, a teammate's token, tell them now, before the cleanup. It is an awkward message and a much better one than the alternative version sent next week.
How fast do I actually need to move?
For a public repo: now, as in before lunch. Exposed keys get found by automation in minutes to hours, not weeks. For a private repo, the clock is slower but still running; private repos become public, get forked into unexpected places, and get cloned onto laptops. Rotation takes ten minutes either way, so the honest answer is the same for both.
Step 2: Assume it was seen
It would be comforting to think nobody noticed your key in the twenty minutes it was public. Plan as if someone did. Automated scrapers watch public repositories continuously, and the supply of leaked keys they harvest is enormous: researchers at Wiz found a single exposed database containing roughly 1.5 million API keys. Yours would not have been special, which cuts both ways: nobody was hunting you, and the bots do not care.
So, with the key already rotated: open the provider's usage logs and billing page and look for anything you did not do. Requests at odd hours, resources you never created, spend you do not recognize. If the key could send email or messages, check the outbox. What abuse looks like varies by key: payment keys get probed with small test charges, AI keys get drained into someone else's product, cloud keys get spent on compute, email keys send spam that lands on your domain's reputation. Knowing which of those you are looking for makes the logs faster to read.
If you find activity, the provider's support team has seen this before; tell them the key was exposed and when. If money was spent, say so plainly and ask about it. Billing teams deal with exposed-key abuse all the time, and the conversation goes better the sooner you start it.
Step 3: Now clean the history
Deleting the file and pushing does not work. Git's whole job is remembering, and the key is still in every commit that ever contained it, one git log -p away. To actually remove it you rewrite history, and GitHub's own guide recommends git filter-repo for the job, specifically version 2.47 or later, which has a flag built for this:
git filter-repo --sensitive-data-removal --invert-paths --path PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA
That removes the named file from every commit. Force-push the rewritten history afterwards, and anyone else working on the repo will need to re-clone. Read GitHub's page before you run it; rewriting history is one of the few git operations without an undo button, and their walkthrough covers the edges this article should not compress.
If the key was pasted inline in a source file rather than living in its own file, the same tool handles that case too, and GitHub's guide covers it. Either way, work from a fresh clone so a mistake costs you nothing, and check the result before pushing: search the rewritten history for the key and confirm it is gone.
The same page is honest about the limits, and you should be too: after a purge, the data can live on in clones and forks, in cached views on GitHub reachable by commit hash, and in pull requests that referenced it. In GitHub's words, you cannot remove sensitive data from other users' clones of your repository. GitHub Support can clear cached views on request. None of that is a reason to skip the cleanup; it is the reason rotation was step one and not step three.
Step 4: Stop the next one
You are past the emergency. Twenty more minutes makes this the last time.
Move every secret out of the code. Environment variables, loaded from a .env file that is listed in .gitignore, with a committed .env.example carrying placeholder values so future-you remembers what goes where. This prompt does the sweep and, importantly, checks history for the keys you do not remember committing, because the key you know about is rarely the only one:
A secret was just exposed in this repository, and I have already rotated it at the provider. Now help me clean up and prevent the next one.
1. Search the entire repository and its full git history for every hardcoded secret: API keys, database passwords, tokens, connection strings. List each with its file path and whether it is in the current code, only in history, or both.
2. Move every secret in the current code into environment variables loaded from .env. Add .env to .gitignore, and create a .env.example with placeholder values.
3. Produce a rotation list: every distinct secret that ever appeared in any commit, so I can confirm each is rotated. Include the ones only in history; those still count as exposed.
4. Tell me exactly which file paths I need to purge from git history so I can run the removal tool against them.
Do not attempt to rewrite git history yourself. Report, clean the working code, and give me the lists. One repeat mistake to not make in the next ten minutes: committing the replacement key while wiring it up. It happens more than you would think, usually in the same commit that removes the old one. The new key goes in the environment, the code reads process.env or your stack's equivalent, and nothing shaped like a credential goes in the diff.
Put deployment secrets where they belong. Your hosting platform has an environment variables section in its project settings; that is where production keys live, entered once, never committed. Local development reads the gitignored .env. If you find yourself pasting a key into a second place, that is the design asking to be fixed, not a step to repeat.
Let GitHub catch what slips through. For public repositories, GitHub's secret scanning runs automatically for free, and push protection for user accounts is on by default, blocking a recognized secret before the push lands. Do not turn those off, and do not rely on them alone either: they recognize known key formats, not every string that matters to you.
The question nobody can answer for you
Was the key copied before you rotated it? No scan, log, or support ticket can prove it was not. That is not a reason to worry forever; it is the reason the runbook is ordered the way it is. Rotation is the one step that works even in the worst case, which is why it came first, and why you did it already.
One more question that comes up
Should I just delete the repository?
It is tempting, and it does less than it seems: forks and clones survive the deletion, and the key is still exposed. Deleting the repo also costs you your history, issues, and stars for a problem that rotation already solved. Follow the steps instead; the repo is fine.
Once the adrenaline fades, this incident is just a nudge to check the rest: the full security checklist covers what else tends to hide in AI-built apps, and if Cursor wrote yours, the Cursor checklist is the shorter targeted version. The key is rotated, the history is clean, the next leak bounces off push protection. Make sure it's actually done, then go back to building.