Security basics
Your API key leaked. Here is the order to do things in
An API key leaked and you want to know what to do first. Not every key in your frontend is one, and the order matters more than the speed.

In short
- If an API key leaked, what to do first depends on which key it is. Publishable keys belong in your frontend and need nothing at all.
- For a real secret, work out whether the key can spend money. That is the only part of this with a clock on it.
- Stopping a key and replacing a key are two different controls at every provider, and most of them give you a window where both keys work.
- Then read the provider log for the window the key was out, and leave your git history alone until the key is dead.
The message usually comes from a scan report, from GitHub telling you a secret turned up in one of your repositories, or from somebody who pressed F12 on your site and sent you a screenshot. However it reached you, you now know an API key of yours has leaked and you do not know what to do first.
Here is the part guide after guide gets wrong: they give you one answer for every key, and the answer is always rotate. Some of those keys are supposed to be public and need nothing done to them. Among the rest, some are quietly running up a bill while you read this, and some did all their damage on the day they shipped. Those are three different situations, and the first move is different in each.
First, is the key actually a secret?
Often it is not, and the numbers are lopsided enough to say so plainly.
We scanned 31,056 live apps and read the JavaScript each one ships to the browser. The check that looks for credentials answered on all of them, and 1,332 came back with at least one key worth flagging. 1,142 of those were Google API keys, which is the one type among them that is usually sitting exactly where it belongs.
A Google API key in a web page is how Google Maps works. The key says which project to bill, and what keeps a stranger from billing you with it is the restriction on the key rather than the secrecy of it. Google's own guidance is blunt about both halves: "Unrestricted API keys are insecure", and "You are financially responsible for charges caused by abuse of unrestricted API keys." The fix for a key like that is to open it in the Cloud console and add two restrictions, one naming your website and one naming the APIs you actually call. The key string never changes.
The other family that belongs in the browser is the publishable keys. A Stripe
pk_live_ key, and a Supabase sb_publishable_ or anon key, are built to be
read by every visitor you have.
Which of your keys are safe in your frontend
is the four-character version of that test.
If you would rather not go through your bundle by hand, our free scan reads your live site, lists the keys it can see from outside, and says which kind each one is: scan your app. It takes about 20 seconds and needs no account.
My API key leaked. What do I do first?
Work out whether the key has a meter on it.
A metered key bills you per request. Google's, OpenAI's, Anthropic's, and an AWS
key with the wrong permissions behind it, are all meters: somebody else's
traffic lands on your invoice, at a rate they choose and you cannot see. An
unmetered key reads or writes your data instead. A Supabase service_role key
is the clearest example, and nothing about it costs money by the hour.
That single question sets the order.
If the key has a meter, stop it now. The feature that used it breaks until you deploy the replacement, and that is the right trade, because the invoice is the one part of this that keeps growing while you plan.
If the key reads data and was published in your bundle, the read already happened. Every visitor who loaded the page has a copy, and so does every crawler that went looking for that string. Nothing gets worse while you work out the right order, and the thing worth getting right is not locking yourself out of your own app on the way.
What each kind of key can actually do
| Key | Spends money | Reads your data | Can you restrict it instead? | Does replacing it break your app? |
|---|---|---|---|---|
Supabase sb_publishable_ or anon | No | Only the rows your rules allow | Belongs in the browser | Not applicable |
Stripe pk_live_ | No | No | Belongs in the browser | Not applicable |
Google AIza… | Yes, on your Cloud bill | No | Yes, and Google says to try that first | Restricting it does not. Rotating it can. |
| OpenAI or Anthropic key | Yes, at a rate a stranger sets | Files and assistants in the project | No publishable variant exists | Yes, until a server of yours holds it |
Stripe sk_live_ or rk_live_ | Yes | Yes, customer and payment records | A restricted key is the narrow version | No, there is a seven-day window |
AWS AKIA… plus its secret half | Yes, including compute by the hour | Your S3 buckets | Deactivate it, which is reversible | No, you can hold two keys at once |
Supabase sb_secret_ or service_role | No | Every row in every table | No | Yes, on an older project |
Two notes on that table, because both change what you do next.
An AWS access key is two strings, an identifier beginning AKIA and a secret
half, and AWS requires both together to sign a request. So an AKIA string on
its own in a bundle cannot be used, and the reason to treat it as urgent anyway
is that the two halves are nearly always pasted in together. Open the file and
look for the second one before you decide which case you are in.
The per-provider detail lives with each provider: an OpenAI key, a Google API key, a Stripe secret key, and a Supabase service_role key, which is the one with a procedure of its own.
Stopping the key and replacing the key are two different buttons
Every provider in that table gives you both, and a panic reaches for the second one.
- Google. Restricting a key does not change the key string, so your app carries on working. Their security guidance puts this before everything else: "First try to restrict your API keys", and rotation is the third option down, for when a restriction is not possible.
- Stripe. Expire key stops a key on its own, with no replacement involved. Their position on when to use it has no hedge in it: "If a restricted or secret API key is exposed or compromised, rotate it immediately even if you aren't sure anyone saw it." They also separate the two words everybody uses interchangeably. Exposure is the key becoming visible somewhere it should not have been. Compromise is evidence that somebody used it.
- AWS. Deactivate is the control, and the useful part is that it can be undone. AWS says not to delete the old key at all while you are still checking: "we recommend that you do not immediately delete the first access key. Instead, choose Actions and then choose Deactivate." If something you forgot turns out to need it, you switch it back on.
- Supabase, on an older project. There is one switch, and it covers both
legacy keys.
anonandservice_roleare signed by the same secret, so disabling the one you are trying to kill also stops the one your frontend is using. The four steps that avoid that are worth reading before you touch the switch rather than after.
Reading the log for the window the key was out
The window opens with the deploy that first shipped the key and closes when you stopped it. That date range is what you filter the provider's log to.
Every provider in the table keeps one. Stripe shows request logs for a single key, from the overflow menu beside that key on the API keys page. AWS puts a last-used date on each access key in the IAM console with no setup at all, and records the calls themselves in CloudTrail. Google charts usage per key in the Cloud console, which is also the reading its own guidance tells you to do before you change anything about a key. Supabase keeps API and database logs for the project, and narrowing the window on a Supabase key covers what to look for in them.
What you are looking for is traffic you cannot account for: requests to tables or endpoints your app never touches, volume at hours when nobody was using it, deletes nobody made. Then look at the data itself, because a support message about a record that changed on its own is how most of these are actually discovered, and it arrives weeks later.
You often cannot be certain, and Stripe says as much about its own detection: "Stripe doesn't guarantee detection of all exposed or compromised keys." So "nothing in the log" is a result, and it is worth writing down with the date range beside it.
Rotating without taking your app down
Three of the four providers above hand you a window where the old key and the new one both work, which is what stops this being an outage.
Stripe. "When you rotate a key in the Dashboard, both the old and new keys work for up to 7 days." The dialog also has a Now option, and their docs are explicit that if you choose it the old key is deleted, which is the button for the metered emergency rather than for a tidy migration. Their advice on when to let the old one go is a measurement instead of a date: check its request logs, and expire it only after its request volume has been at zero for a few hours or days.
Google. Rotating creates the new key carrying all of the old key's restrictions, and, in their words, "both the old and new key are accepted" during the window while you move your apps across. If you delete the old key too early and something breaks, there is a way back: a deleted Google API key can be undeleted within 30 days.
AWS. The sequence is in their documentation and it starts with the new key: create the second access key while the first is still active, move every application onto it, check the last-used date on the old one, deactivate, and only then delete. One ceiling to plan around is that an IAM user can hold a maximum of two access keys, so a third application still on an old key has nowhere to go.
Supabase, on an older project. No window. Direct rotation of the legacy
anon and service_role keys is no longer supported, so invalidating one is a
migration onto the new key pair, and the order of the four steps is what keeps
the app up.
It is still in your git history
It is, and GitHub's documentation on that opens by telling you to do something else first.
Their page on removing sensitive data sends you back to the key: "if the sensitive data you need to remove is a secret (e.g. password/token/credential), as is often the case, then as a first step you need to revoke and/or rotate that secret", and then, "Once the secret is revoked or rotated, it can no longer be used for access, and that may be sufficient to solve your problem. Going through the extra steps to rewrite the history and remove the secret may not be warranted."
The reason they say that is what a force push does not reach. After you rewrite your history, the old commits are still there "In any clones or forks of your repository" and "Directly via their SHA-1 hashes in cached views on GitHub". Support can clear the cached views and the pull request references if you ask, and they draw their own line: they "will only assist in the removal of sensitive data in cases where we determine that the risk can't be mitigated by rotating affected credentials." A fork keeps its copy either way, and GitHub cannot give you the fork owner's contact details.
So the order they describe is the practical one: revoke the key, then decide whether rewriting the history is worth the side effects. Revoking reaches copies a rewrite cannot, including the one in a clone you do not know about and the one in a screenshot somebody kept.
Keeping the next one out of the bundle
A key gets into your bundle through a deploy, and deploys keep happening. Every one of them is another chance for a value you put in a secrets panel to end up in a file the browser downloads, which is why a check you ran last month describes last month's app.
Our free scan answers the question you came here with.
- the nine checks against your live URL, in about 20 seconds
- every key it can see from outside, each one classified rather than just matched
- a grade and the findings, with no account
Reeve Monitor runs those checks again without you asking.
- all nine checks every hour, on up to three apps
- a message when a result changes, so the key that went out in last night's deploy does not wait for you to look
- whether the app is up, every 60 seconds
- a monthly report of what it saw
Reeve Care keeps a copy of your Supabase database, for the keys that can write.
- an encrypted copy every night, kept where your project cannot reach it
- each copy verified before it counts, by counting the rows in every table
- a one-click restore when you need one
- your uploaded files as well, once you connect a Storage credential
- everything Monitor does
A leaked key that can only read leaves your data where it was. A service_role
key, or an AWS key with write permissions, can empty a table, and no amount of
rotating afterwards brings the rows back.
The day an AI agent deleted a production database
is what that looks like from the inside.
What to do right now
What to do
- Identify the key before you touch it. A Supabase
anonorsb_publishable_key and a Stripepk_live_key belong where they are, and of the 1,332 apps where we found a key worth flagging, 1,142 had a Google API key, which wants a restriction instead of a rotation. - Ask whether it has a meter. If somebody else's requests land on your bill, stop the key now and let the feature break.
- Use the stop control as well as the replace one: Expire key at Stripe, Deactivate at AWS, a website and API restriction at Google.
- Then replace it inside the provider's window. Stripe gives you seven days with both keys live, Google accepts both while you migrate, and AWS lets you hold two access keys at once.
- Read the provider log for the window between the deploy and the stop, and write down what you found, including "nothing".
- Leave the git history until last. Once the key is revoked, GitHub's own guidance says rewriting history may not be warranted at all.
If you would rather work through the whole thing as a list, the 10-minute security checklist covers this and the other things worth switching off in a newly launched app.
FAQ
My API key leaked. What do I do first?
Work out whether the key can spend money. A key that bills you per request is costing you something right now, so stop it immediately and accept that the feature using it breaks for a few minutes. A key that only reads data has already been read if it was public, so take the time to do the replacement in an order that does not lock you out of your own app.
Should I revoke or rotate first?
Revoke first if the key has a meter on it. Stopping the old key and issuing a new one are two separate controls at every provider, and only the first one closes the hole. The exception is a key you can restrict instead: Google tells you to try a website and API restriction before you rotate a Google API key at all.
How do I tell if someone used my key?
Narrow the window to between the deploy that shipped the key and the moment you stopped it, then read the provider log for that range. Stripe shows request logs per key, AWS shows a last-used date on the access key and records calls in CloudTrail, Google charts usage per key, and Supabase keeps API and database logs. You are looking for requests to things your app never touches and traffic at hours nobody was using it. You often cannot be certain, and that is a normal outcome.
Will rotating my key break my app?
Usually not, if you use the window the provider gives you. Stripe keeps the old and new key working together for up to seven days. Google issues the new key with the old key restrictions and accepts both while you migrate. AWS lets you hold two access keys at once so the new one is live before the old one goes. The exception is an older Supabase project, where the switch that disables the legacy keys takes your frontend key with it.
The key is in my git history. Is deleting the file enough?
No, and rewriting the history is probably not the answer either. GitHub documentation says to revoke or rotate the secret first, and that once you have, going through the extra steps to rewrite history may not be warranted. A force push does not reach the copies in forks and clones, or the cached views reachable by commit hash. Making the key useless covers every copy at once.