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

  1. Revoke it. OpenAI: platform.openai.com โ†’ API keys โ†’ the โ‹ฏ next to the key โ†’ Delete. Anthropic: console.anthropic.com โ†’ API keys. Takes effect immediately.
  2. Create a replacement โ€” project-scoped, not account-wide, if you're offered the choice.
  3. Set a spend limit before you go anywhere else. Two clicks, and it's the difference between a bad week and a bad month.
  4. 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 riskAt 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:

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:

5. Cap the damage before it happens

The single highest-value thing on this page, and it takes a minute:

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.

Scan my project โ€” free