My OpenAI API key is exposed
Good news first: this is the least bad key to leak. Nobody gets your data, nobody gets your users, and revoking it takes two minutes with no side effects. The bad news is why it leaked โ and that part usually needs a code change, not a new key.
Everything here applies equally to an Anthropic key
(sk-ant-), a Google AI Studio key, or a Groq or Mistral key. The
dashboards differ; the shape of the problem doesn't.
Do this first
- Revoke it. OpenAI: platform.openai.com โ API keys โ the โฏ next to the key โ Delete. Anthropic: console.anthropic.com โ API keys. Takes effect immediately.
- Create a replacement โ project-scoped, not account-wide, if you're offered the choice.
- Set a spend limit before you go anywhere else. Two clicks, and it's the difference between a bad week and a bad month.
- Then find out why the key was reachable. Section 3. This is the actual job.
1. What's actually at risk (less than you think)
It's worth being precise, because people lose an afternoon to the wrong worry:
| Not at risk | At risk |
|---|---|
| Your past conversations. An API key can't retrieve completions you already made. There's no "read my history" endpoint to abuse โ whoever has the key can make new calls, not read old ones. | Your money. Directly, at whatever rate they can burn tokens. Leaked keys get collected and resold in bulk, and the buyers are running production traffic on them. |
| Your users' accounts. The key has nothing to do with your app's auth. Nobody logs in as anyone. | Your rate limits. Someone else's traffic on your organisation means your real users hit 429s. |
| Your customer data. Unless you put it in a vector store or file that the same key can list โ worth a look if you use the Files or Assistants APIs. | Your reputation with the provider. If the key gets used for something against their policy, it's your organisation that gets flagged. |
This is why the response is different from a leaked database or payments key. There's no forensics to do on your data and nobody to notify. Cap the spend, kill the key, fix the cause.
2. Revoking costs you nothing, so don't hesitate
Unlike a Supabase JWT rotation (which signs out every user) or a Stripe key roll (which can drop live payments), revoking an LLM key breaks exactly one thing: requests using that key. Deploy the replacement and it's over.
So there's no reason to wait, to schedule it, or to "check first whether it was really public". If you're unsure, revoke. The worst case is five minutes of a broken feature.
Note that you cannot read an existing key back out of either dashboard โ only the prefix is shown. If you're trying to work out whether the key in your repository is the same as the live one, match the last four characters, or just revoke both and start clean.
3. The real problem is almost never the key
Here's the pattern, and it's near-universal in AI-built apps: the key leaked because the browser was calling the model provider directly. Rotating gives you a fresh key in exactly the same position โ public โ and it leaks again on the next deploy.
Two shapes to look for.
The one with a warning label in the name
const openai = new OpenAI({
apiKey: import.meta.env.VITE_OPENAI_API_KEY,
dangerouslyAllowBrowser: true, // โ this
})
The OpenAI SDK refuses to run in a browser unless you explicitly override
it, and it named the override dangerouslyAllowBrowser so nobody
could claim they weren't told. Anthropic's SDK has
dangerouslyAllowBrowser too. An AI coding tool hits the error,
reads the fix in the message, and adds the flag โ the error goes away and the
key goes public.
The one with no warning at all
Any environment variable prefixed VITE_,
NEXT_PUBLIC_, REACT_APP_, EXPO_PUBLIC_
or PUBLIC_ is compiled into the JavaScript you ship.
That's the entire purpose of the prefix. Your .env file being
gitignored changes nothing: the value is in the built bundle, and anyone can
read it with View Source.
If your key is in a VITE_-style variable, it is already
public to every visitor your site has ever had โ whether or not it's also in
git. Revoking the key is step one; the variable has to stop existing.
4. The fix: put a server in the middle
The key belongs on a server. The browser calls your endpoint, your endpoint calls the model. It's one small file, and every host you're likely to be on has a place to put it:
- Next.js: a route handler in
app/api/chat/route.ts - Vercel / Netlify (any framework): a serverless function
- Supabase: an edge function, with the key in its secrets
- Vite SPA with no backend: this is the case that needs a real decision โ add a small serverless function, or move to a framework that has somewhere to run server code. There is no client-side arrangement that keeps the key secret. Obfuscation, encoding it, splitting it in half: all still public.
While you're adding that endpoint, add the two things that make it survive contact with the internet, because an open proxy to a paid model is its own kind of leak:
- Check who's calling. Require a logged-in session, or at minimum something that isn't just "anyone who found the URL".
- Rate limit it. Per user and per IP. Without this, you've replaced a leaked key with a free public API that bills you.
5. Cap the damage before it happens
The single highest-value thing on this page, and it takes a minute:
- Spend limits. OpenAI: Settings โ Organization โ Limits, set a monthly budget and a notification threshold. Anthropic: Settings โ Limits. This is a hard ceiling on the worst case.
- Project-scoped keys. OpenAI keys can be scoped to a single project with its own budget โ one leak stops being an account-wide problem.
- Prepaid credits rather than a card on file, if the provider offers it. Your exposure is capped at the balance.
- Usage alerts to an address you actually read. Abuse shows up as a spike, usually overnight, usually on the most expensive model available.
- One key per app. Shared keys mean you can't tell whose traffic is whose, and revoking breaks four things instead of one.
6. Checking whether it was used
Both providers break usage down per key: OpenAI under Usage (group by API key), Anthropic under Usage in the console. Look at the window between the commit that exposed it and the moment you revoked.
What abuse looks like: a step change rather than a ramp, traffic at hours when your app is quiet, and requests to models your code never calls โ a chat app that only ever uses a small model suddenly running the flagship one is the giveaway.
If the bill is already large, contact the provider's support and tell them the key was public and when. They can see the traffic pattern, they've handled it many times, and credits for clearly fraudulent usage are not unheard of. You get a much better hearing before the invoice than after it.
7. The prompt
Paste this into whatever built the app:
Find every place an LLM API key appears in this repository โ OpenAI, Anthropic, Google, or any other provider โ including markdown files, scripts and environment examples. Remove every client-side use: delete any dangerouslyAllowBrowser flag, and remove any key from a VITE_, NEXT_PUBLIC_, REACT_APP_ or EXPO_PUBLIC_ variable. Move the model calls into a server-side route that reads the key from process.env, requires an authenticated session, and rate limits per user. Then list every file you changed and every key you found, so I know which ones to revoke.
That last clause matters more than it looks. Revoke every key it finds, not just the one that sent you here โ if one was reachable, the others in the same repository probably were too.
Check whether there's another one
vibecheck scans your project for exposed keys, secrets compiled into
frontend bundles, and dangerouslyAllowBrowser โ and explains
each one in plain English, with a fix prompt to paste back into your AI
coding tool.