What to do in the first hour after an API key leaks
Revoke or restrict the key, check what it was used for, find how it got out, and limit the replacement. Steps for Google Cloud, AWS, OpenAI, Anthropic and more.
· Updated · 9 min read
- incident response
- api keys
- google cloud
- aws
- openai
- anthropic
On this page
You found an API key somewhere it shouldn't be. Maybe it was a public repo, a JavaScript bundle, a screenshot or a support ticket. Maybe the provider emailed you about it, or the bill did.
Work in this order. Stop the key first, because every minute it stays live can cost money. Then find out what it was used for, and then how it got out. Cleaning up git history comes last, because the key was copied the moment it went public.
Before you start
- Assume it has been used. Bots scan public repositories and websites for keys. Treat a key that was public for any length of time as compromised, even if it was only up for a minute.
- Keep a timeline as you go. Note when you found the key, where it was, and what you did at what time. If you end up asking the provider to waive charges, they will ask for it.
- Don't paste the key anywhere new. Not into chat, the incident doc or a support ticket. Refer to it by its name, its ID or its last four characters.
Stop the key
If the key is being abused right now, revoke it now and accept the outage. A feature that's down for twenty minutes costs less than twenty more minutes of someone else's usage on your account. If you see no unusual usage yet, you can create the replacement first, deploy it, and then revoke the old key.
Removing the key from your code doesn't stop anything. The copy that leaked keeps working until the provider stops accepting it.
Google Cloud, Gemini, Maps and Firebase
Google Cloud API keys live in a project, under APIs & Services > Credentials. Gemini keys made in Google AI Studio are API keys in a Google Cloud project too, and so are the browser keys Firebase creates for your app.
# Find which key this is. Prints the key's resource name, which ends in its ID.
gcloud services api-keys lookup "THE_LEAKED_KEY"
# Delete it.
gcloud services api-keys delete KEY_ID --project=PROJECT_IDYou can restore a deleted key within 30 days with gcloud services api-keys undelete KEY_ID, so deleting is less final than it sounds.
If you can't delete the key yet, restrict it to the one API it needs. A key with no API restrictions can call every API enabled in its project that accepts API keys. Until June 2026 that included Gemini, which is how public Maps keys ended up paying for it, as Truffle Security found in keys published on public websites. Can your Google Maps API key call Gemini? covers what changed and how to check your keys.
# Replace the key's API restrictions with a single API (here, the Maps JavaScript API).
gcloud services api-keys update KEY_ID --project=PROJECT_ID \
--api-target=service=maps-backend.googleapis.comFirebase web config keys are meant to ship inside your app, so for those the fix is restricting the key to the Firebase APIs it uses, not hiding it.
If what leaked was a service account key (a JSON file with a private_key field), treat it as an account compromise. It can do anything the service account's roles allow. Disable or delete the key under IAM & Admin > Service Accounts > Keys.
AWS
AWS access keys belong to an IAM user. Deactivate the key first. You can turn it back on if something critical breaks, and delete it once the replacement is live.
aws iam update-access-key --user-name USER_NAME \
--access-key-id AKIAXXXXXXXXXXXXXXXX --status InactiveDeactivating the key doesn't end sessions the attacker already has. Temporary credentials they got from it through GetSessionToken or AssumeRole keep working until they expire. If CloudTrail shows those calls (see the next section), attach a policy to the user that denies every action, and use Revoke active sessions on each role the key assumed.
If AWS found the key before you did, you may already have a support case open and an AWS managed policy whose name starts with AWSCompromisedKeyQuarantine attached to the user. That policy blocks some actions. It doesn't replace rotating the key.
OpenAI
Delete the key on the API keys page of the OpenAI platform and create a new one. If the key leaked to a public GitHub repository, OpenAI may already have disabled it through GitHub's secret scanning. Delete it anyway. Keys belong to a project. If your apps share a project, give each its own, so the next leak is limited to one app's project.
If you pay with prepaid credits and auto recharge is on, turn auto recharge off until you're done. Spending then stops when the balance runs out.
Anthropic
In the Claude Console, open API keys and disable or delete the key. Anthropic deactivates keys found in public GitHub repositories on its own, so if that's where it leaked, check whether it's already off. Keys belong to a workspace, and a workspace can have its own spend and rate limits, so put the replacement in a workspace with limits that match what the app really uses. The same advice about auto reload applies here.
Resend
Delete the key under API Keys in the Resend dashboard. An abused Resend key sends mail from your verified domains, so the damage is spam under your name and your domain's sending reputation. Create the replacement with Sending access only, limited to the one domain it sends from.
Supabase
The anon or publishable key is meant to be public. Row level security is what protects your data from it. The key that matters is the service role or secret key, because it bypasses row level security entirely.
- With the newer API keys (
sb_secret_...), delete the leaked secret key under Project Settings > API Keys and create another. - With the legacy JWT keys, rotating means changing the project's JWT secret. That invalidates the anon key and your users' current sessions too, so deploy the new keys right after.
Everything else
The move is the same for any provider. Find the key in the dashboard, revoke or disable it, and issue a new one with the narrowest scope the provider offers.
Find out what it was used for
Billing is the slowest place to look, because providers often report spend hours after the usage happened. Check request metrics and audit logs first.
Google Cloud
- APIs & Services > Metrics shows request counts per API, and you can filter by credential to see one key's traffic.
- Billing > Reports, grouped by SKU over the last few days, shows what the usage cost. Look for SKUs you don't normally pay for, such as Gemini models on a project that only uses Maps.
- For a leaked service account key, Logging > Logs Explorer has the Admin Activity audit logs: which resources it created, changed or deleted, and which APIs it enabled.
AWS
# When the key was last used, and for which service in which region.
aws iam get-access-key-last-used --access-key-id AKIAXXXXXXXXXXXXXXXX
# Everything the key did, region by region.
for r in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do
echo "== $r"
aws cloudtrail lookup-events --region "$r" \
--lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAXXXXXXXXXXXXXXXX \
--query 'Events[].[EventTime,EventSource,EventName]' --output text
doneCloudTrail event history covers the last 90 days of management events, separately in each region. Attackers often work in regions you never use, which is why the loop checks them all.
Look for anything the key created, because deleting the key doesn't delete it:
- new IAM users, access keys, roles or login profiles
- EC2 instances, Lambda functions or ECS tasks, often in unfamiliar regions
- Bedrock model access or invocations, and SES sending
- requests to raise service quotas
Cost Explorer with daily granularity, grouped by service and then by region, shows where the money went.
OpenAI and Anthropic
Both usage pages can break usage down by API key. Look at the leaked key from the time it went public, and at which models it called. Unfamiliar models, or traffic at hours your app is quiet, mark the requests that weren't yours.
Find out how it got out
Revoking the key fixes this leak. Finding the source stops the next key from leaking the same way.
To see every commit that added or removed the key, on every branch:
git log --all --oneline -S 'A_UNIQUE_CHUNK_OF_THE_KEY'Then scan the whole history for other secrets with a tool like Gitleaks or TruffleHog. When one key leaks from a repo, others often sit next to it.
Check your built frontend too. Any environment variable your bundler inlines is public. That's every VITE_ variable in Vite, every NEXT_PUBLIC_ variable in Next.js and every EXPO_PUBLIC_ variable in Expo. Key prefixes make the search quick:
| Key | Starts with |
|---|---|
| Google Cloud API key (Gemini, Maps, Firebase) | AIza |
| AWS access key ID | AKIA (long-term), ASIA (temporary) |
| OpenAI | sk-, project keys sk-proj- |
| Anthropic | sk-ant- |
| Resend | re_ |
| Supabase secret key | sb_secret_ |
grep -rnE 'AIza|AKIA|sk-ant-|sk-proj-|sb_secret_' dist/Other places keys leak from:
- CI logs that print environment variables or a failing command's full arguments
- Docker images that copied a
.envfile into a layer - npm and PyPI packages published with a
.envor config file inside - screenshots, screen shares and recorded demos
- issue trackers, chat threads and AI coding tool transcripts
Rewriting git history with git filter-repo or BFG is worth doing so scanners stop flagging the old commits. It isn't the fix. Forks, clones and caches keep the old commits. The fix is that the leaked key no longer works.
Restrict the replacement
The new key will leak one day too. Set it up so that day is cheap.
- One key per app and environment. Then you can kill one key without touching the others, and per-key usage tells you exactly which app is affected.
- On Google Cloud, use both kinds of restriction. API restrictions limit which APIs the key can call. Application restrictions limit where it works: HTTP referrers for browser keys, IP addresses for servers, and app restrictions for Android and iOS. Referrer restrictions stop a key working on someone else's website, but a script can send any Referer header it likes, so API restrictions and quotas are what limit the damage.
- Lower the quotas. Each Google Cloud API has quotas under APIs & Services > the API > Quotas. Set requests per day to a bit above your real peak.
- Know what a budget does. Budgets on Google Cloud and AWS send alerts. They don't stop usage unless you add automation that does. Set them anyway, at a level you'd want to hear about.
- Cap AI spend. Use workspace or project limits on OpenAI and Anthropic, a spend cap on Gemini, and prepaid credits without auto reload where that suits your app. Billing spike alerts and spend caps has the steps for each.
- Avoid long-lived AWS keys. Roles, IAM Identity Center and OIDC federation for CI (GitHub Actions can assume an AWS role without stored keys) leave nothing to leak.
- Catch the next one before it's pushed. Turn on GitHub secret scanning and push protection, and run a secret scanner in a pre-commit hook.
# A Maps key for one website: one API, one site.
gcloud services api-keys create --project=PROJECT_ID \
--display-name="maps-web-prod" \
--api-target=service=maps-backend.googleapis.com \
--allowed-referrers="https://example.com/*"If there's already a bill
Export the usage now, as CSV where the provider offers it, before anything ages out of the console. For a Gemini bill, unexpected Gemini API bill goes through finding the cause and asking Google for an adjustment. Then open a billing case with the provider. Bring the timeline: when the key leaked, when you found it, when you revoked it, and which usage wasn't yours.
Whether you get a refund is the provider's decision. A clear timeline and the evidence that you closed the hole quickly is the strongest case you can make.
Next time, don't wait for a person
Every step above starts with someone noticing. How to automatically revoke an abused API key shows how to switch off a key as soon as its usage spikes. Keyll watches every key you connect and blocks the one being abused, and your other keys keep running.
Checklist
- Leaked key revoked, deactivated or restricted
- Replacement deployed, with API restrictions and quotas
- Usage since the leak checked, per key
- Anything the key created found and removed
- Billing checked and budget alerts set
- Source of the leak found and closed
- Other secrets in the same place rotated
- Timeline and usage exports saved