A Key Just Leaked — Rotate, Audit, Monitor (in That Order)
A secret key leaked? Here's the incident runbook: confirm it's actually secret, rotate it, audit for abuse, then monitor — because keys re-leak on the next deploy.
A key just leaked. Maybe a scanner flagged it, maybe GitHub emailed you a secret-scanning alert, maybe you spotted it in your frontend bundle. Your pulse is up and you want to do something. The instinct is right; the trick is doing the right things in the right order.
There's a clean sequence: confirm, rotate, audit, monitor. That last step matters because in AI-built apps the same key has a habit of leaking again on the next deploy. Skip the order and you'll do the slow steps before the fast one that stops the bleeding.
This is the runbook. Work it top to bottom; most of it takes minutes.
⚡ TL;DR
- Step 0, confirm: make sure it's actually a secret key. A Stripe
pk_live, the Supabaseanonkey, or the Firebase web config is public by design — not an emergency.- Step 1, rotate: generate a new key and revoke the old one in the provider's dashboard. This is the one step that immediately makes the leak harmless. Do it first.
- Step 2, audit: check the provider's usage logs for abuse, scope what was exposed, and scrub the key out of your git history.
- Step 3, monitor: keys re-leak when your AI builder ships new code, so check continuously, not once.
Step 0: confirm it's actually a secret key
Before you rotate anything, spend thirty seconds confirming the leak is real, because rotating a public-by-design key wastes time and can break your app. It's the most-skipped step, and the one that separates a calm fix from a scramble.
Ask: is this key meant to be public? Three common keys are, and finding them in your frontend is normal:
- Stripe
pk_live_…— the publishable key. It identifies your account to Stripe's checkout but can't move money or read customers. Built to ship in the browser. - Supabase
anonkey — the anonymous public key. Supabase apps query the database from the browser, so this key has to be there. Your protection is Row Level Security (RLS = the rules deciding which rows each user can read), not secrecy. - Firebase web config (
apiKey) — public by design despite the name. It identifies your project; your protection is Firebase Security Rules.
If the leaked key is one of those, you're done here — there's no emergency. The deeper "why" is in the complete guide to exposed secrets in frontend code.
Now the keys that do demand the runbook:
- Stripe
sk_live_…— the secret key. Full account access. - Supabase
service_role— the admin key. Bypasses RLS, reads and writes every row. - OpenAI / Anthropic keys — secret and metered. Whoever holds one spends your money and can read data passing through.
The fast tell for Stripe: pk_ is public; sk_ is secret and an emergency. One letter. We dig into that in did I leak my Stripe key?.
⚠️ Watch out
A leaked
service_rolekey is the worst case here. Because it bypasses RLS, every row in every table was readable by whoever had it — the same root cause behind real incidents like Moltbook, where missing RLS on a Supabase backend exposed roughly 1.5 million API keys and around 35,000 user emails. Treat aservice_roleleak as a full-database exposure, not a single-key problem.
Step 1: rotate the key
Confirmed it's a secret key? Rotate it now. Rotation means generating a brand-new key and revoking the old one. This is the highest-leverage action in the process: the moment the old key is revoked, every copy an attacker scraped becomes a dead string. The exposure isn't fully resolved, but the bleeding stops.
Where to do it:
- Stripe
sk_live. In the Stripe Dashboard, go to Developers → API keys, roll (regenerate) the secret key, and revoke the old one. Stripe shows the new value once — save it somewhere safe immediately. - OpenAI. In the OpenAI platform under API keys, create a new secret key and delete the leaked one. While you're there, set a hard usage limit so a future leak can't run up an unbounded bill — the cost side is in an exposed OpenAI or Anthropic key is someone else's bill on your card.
- Anthropic. In the Anthropic Console under API keys, generate a replacement and revoke the exposed key. Set spend limits the same way.
- Supabase
service_role. In your Supabase project settings under API keys, rotate theservice_rolekey (and the related JWT secret if your provider exposes it). Rotating invalidates the old token everywhere, so be ready to update your server-side code with the new value.
Two rules while you rotate. First, don't paste the new key back into frontend code — you'd recreate the exact hole you're fixing. The new key goes server-side only: a backend route, a serverless function, or a Supabase edge function, where the browser never sees it. Second, revoke, don't just create. A new key without disabling the old one leaves the leaked copy fully alive.
Step 2: audit what the key touched
The key is dead. Now find out what it did while it was alive. The goal is to scope the damage and close any door the exposure opened.
Check the provider's usage logs. Pull the activity for the window the key was exposed and look for anything you didn't do:
- Stripe — review recent charges and API logs for unfamiliar activity, especially refunds sent to accounts you don't recognize.
- OpenAI / Anthropic — check the usage dashboard for a spike or requests outside your normal pattern. Metered keys get abused for free compute fast, so an unexplained jump is the tell.
- Supabase — for a
service_roleleak, assume every table was readable. Review your logs and treat any sensitive data in those tables as potentially accessed.
Scope the exposure. Note, even roughly: which key, how long it was public, and what it could reach. A service_role key reaches your whole database; an OpenAI key reaches your spend and the data that flowed through those requests. The blast radius tells you whether this is a quiet fix or something that triggers a duty to notify users.
Scrub it from your git history. This is the step people miss, and why a "fixed" key keeps coming back. Removing a secret from your latest code does not remove it from your repository's history — every past commit that held it is still there, readable by anyone with repo access (or anyone, if the repo is public). You can rewrite history to purge it with a tool built for that, like git filter-repo or BFG, but that's fiddly and easy to get wrong — so treat rotation as the true fix. It's the heart of the "api key leaked github" problem: the alert fires because the key is in your history, and the durable answer is a revoked key, not a deleted line.
How to check your own app
Once you've rotated, confirm the old key is dead and the new one isn't repeating the mistake. A two-minute pass:
- Try the old key. Make one request with the revoked key. It should fail. If it still works, the revocation didn't take — redo it.
- Scan your live app for the new key. Open your deployed site, open developer tools (F12), and search the loaded JavaScript (Ctrl/Cmd-Shift-F) for
sk_live_,sk-,sk-ant-, andservice_role. The new key must not appear — if it does, you pasted it back into frontend code. - Confirm it's server-side. The only place the new secret should live is a backend route, serverless function, or edge function the browser never sees.
Not sure if the leaked key is still exposed somewhere?
Run a free, read-only scan of your live app — no install, results in under a minute. Is My Site Hackable? checks your deployed app for exposed keys and tells you what's real and what's a false alarm.
Scan my app free →Step 3: monitor, because keys re-leak
Here's what makes AI-built apps different: you can rotate a key perfectly and have the same leak reappear on your next deploy, because your AI builder writes new code constantly.
Think about how the original leak likely happened: the AI hardcoded a key to make a feature work, or put a secret behind a browser-exposed prefix like VITE_, NEXT_PUBLIC_, or REACT_APP_, or a build config flipped source maps on. Rotating one key fixes none of those root causes, and the next time you ask for a feature that needs a key, the AI may reach for the same shortcut. A working demo with a hardcoded key looks identical to a secure one until someone reads it.
So the final step is not a one-time action — it's a habit. Monitoring means re-checking after deploys:
- Re-scan after meaningful changes, especially anything touching integrations, environment variables, or build settings.
- Keep usage limits in place on metered providers so even a missed leak has a ceiling.
- Watch your provider dashboards for the unexplained spike that signals a key you didn't know was out.
A single point-in-time check tells you about today's build; continuous checking catches the regression three deploys from now. An attacker only has to find one window where a key is live, so close those windows as fast as your AI opens them.
FAQ
What's the very first thing to do when a key leaks?
Confirm it's a secret key, then rotate it. A Stripe pk_live, a Supabase anon key, or a Firebase web config is public by design and not an emergency. If it's a secret key (sk_live, service_role, an OpenAI or Anthropic key), rotating it makes the leaked copy useless immediately — do that before auditing or cleaning up history.
My API key leaked on GitHub. Is deleting the commit enough?
No. Deleting the file or line in your latest commit leaves the key in your repository's history, where it's still readable. The reliable fix is to rotate the key so the exposed value is revoked and worthless wherever it appears. You can also rewrite git history to purge it, but rotation is what neutralizes a key that's already public, so do that first.
How long was the key exposed — does it matter?
It matters for the audit, not the fix. Rotate regardless of how long it was out, because even a brief exposure can be enough for an automated scraper to grab it. The window tells you how hard to look: a key public for weeks warrants a careful review of provider logs; a key caught in minutes still needs rotating but is less likely to have been abused.
I rotated and audited. Why keep scanning?
Because your AI builder ships new code on every deploy and can reintroduce the same leak without warning. Rotating fixes today's exposed key; it doesn't change the habit that created it. Continuous scanning catches the regression when a future build hardcodes a key or flips source maps back on.
The bottom line
When a key leaks, order is everything: confirm it's actually a secret, rotate it to kill the leaked copy, audit the provider logs and your git history, then monitor — because the same gap comes back on the next deploy. The danger lives in not knowing a key is out, and in assuming one fix is permanent. An attacker can find and abuse an exposed secret in minutes, so find it first and keep finding it on every deploy, because your AI ships new code — and sometimes a fresh leak — each time.
Find your gaps before an attacker does.
Is My Site Hackable? scans your deployed app for the exact issues in this article — exposed keys, missing RLS, open buckets — and tells you what's real and what's a false alarm.
Run a free scan →