Abbi Labs

Guides

OpenAI, Claude or Gemini API key exposed in your frontend? What to do now

By Pritesh Ghodge, security engineer · Last updated 31 October 2026

Not sure if your key is in your JavaScript?

The Abbi Labs quick check reads the same files every visitor downloads and looks for OpenAI, Anthropic, Google, Stripe, AWS, GitHub and Supabase secret keys. It shows only a masked prefix, never the full key. No sign-up.

Scan your app free

Weekly AI-app security checklist

One short email a week: a practical check for apps built with Supabase, Next.js and AI tools (RLS, leaked keys, headers and more). Unsubscribe anytime.

AI builders often get a chat or "generate" feature working by calling the AI provider straight from the browser. It works in the preview, so it ships. But the key that request needs is now in your site's JavaScript, and anyone who opens DevTools can copy it and run requests billed to you. You won't notice until the invoice or the usage alert arrives.

This guide is for OpenAI, Anthropic (Claude) and Google Gemini keys. The same steps apply to any paid API key: Groq, OpenRouter, ElevenLabs, Replicate and the rest.

Check in 2 minutes

  1. Open your live site (not the editor preview), then DevTools → Sources (Chrome) or Debugger (Firefox).
  2. Search all files (Ctrl/Cmd + Shift + F) for: sk-proj-, sk-ant-, sk-, AIza, api.openai.com, api.anthropic.com, generativelanguage.googleapis.com.
  3. Then open the Network tab and use the AI feature. If you see a request going straight to api.openai.com, api.anthropic.com or generativelanguage.googleapis.com from the browser, check its headers: an Authorization: Bearer sk-…, x-api-key or x-goog-api-key header (or a key= parameter) means the key is public.

A request to your own /api/chat or a Supabase edge function is the right shape. The key should only appear on the server side of that call.

The usual cause is a variable named VITE_OPENAI_API_KEY or NEXT_PUBLIC_OPENAI_API_KEY. Those prefixes mean "copy this into the browser bundle at build time". More on that: NEXT_PUBLIC_ and VITE_ variables are shipped to every visitor.

Fix it in this order

1. If you see usage you don't recognise, revoke now

Look at the provider's usage page first. If there's traffic you didn't make, delete the key immediately and accept that the AI feature is down until step 3. Then contact the provider's support with what you found.

2. Create a new key, and keep it on the server

Make a new key in the provider's dashboard and put it in a server-only environment variable: no VITE_ or NEXT_PUBLIC_ prefix. In Supabase that's an edge function secret; on Vercel, a normal environment variable used only by an API route.

3. Move the AI call behind your own route

The browser calls your route; your route calls the AI provider. The route should:

// app/api/chat/route.ts (Next.js) - the key never leaves the server
export async function POST(req: Request) {
  const user = await getSignedInUser(req);          // your auth check
  if (!user) return new Response("Sign in first", { status: 401 });
  // ...rate-limit by user.id here...
  const { message } = await req.json();
  const r = await fetch("https://api.openai.com/v1/responses", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,  // no NEXT_PUBLIC_
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ model: "YOUR_MODEL", input: String(message).slice(0, 4000) }),
  });
  return new Response(r.body, { status: r.status });
}

4. Deploy, then delete the old key

Once the new version is live and working, delete the old key. Deleting it is the only thing that makes the copy in old bundles, caches and anyone's notes useless. Removing it from your code doesn't.

5. Put a ceiling on the damage

A prompt for Lovable, Bolt or Cursor

My app calls the AI provider (OpenAI / Anthropic / Gemini) from the
browser, so the API key is in my public JavaScript. Fix it:
1. Move every AI API call into a server route or Supabase edge function.
   The browser should only call my own endpoint.
2. Read the key from a server-only secret. Remove any VITE_ or
   NEXT_PUBLIC_ variable that holds it.
3. In that endpoint, reject requests from users who aren't signed in,
   rate-limit per user, cap input length and max output tokens, and set
   the model and system prompt on the server.
4. Search the whole codebase and the built output for the old key and
   for direct calls to the provider's API, and show me what you found.
Don't put the key anywhere the browser can read it.

Then repeat the DevTools check above on the live site. AI edits sometimes add a second, direct call somewhere else.

Don't stop at the AI key

If one secret ended up in the bundle, check for the others. Stripe secret keys (sk_live_) and the Supabase service_role key are worse than an AI key: they can move money or read every row in your database. See Supabase service_role key exposed? Do this in the next 15 minutes. And make sure your site isn't serving the files keys usually live in: .env, .git and source maps.

Keep it that way

Keys tend to come back with the next AI edit or the next new feature. Abbi Labs checks your live JavaScript for secret keys, along with Supabase tables readable with the public key, exposed .env/.git files and security headers. Monitor re-scans weekly and after every deploy, and emails you only when something new appears.

Run a free scan

FAQ

Is it safe to put my OpenAI API key in a VITE_ or NEXT_PUBLIC_ variable?

No. Variables with those prefixes are written into the JavaScript every visitor downloads, so the key is public the moment you deploy. OpenAI, Anthropic and Google all say to keep API keys out of browser code. Call the AI provider from a server route or edge function instead.

Does hiding the key in an environment variable fix it?

Only if the code that uses it runs on the server. An env var that's read by browser code is copied into the bundle at build time. What matters is where the request to the AI provider is made, not where the key is stored.

Should I delete the key or just rotate it?

Create the new key first, move the call server-side and deploy, then delete (revoke) the old key. If you see usage you don't recognise, revoke the old key straight away even if the feature breaks for a while: every minute it's live costs you money.

Will a spend limit protect me?

It caps the damage, but it isn't a fix. OpenAI says alerts alone don't stop traffic and that a hard limit isn't instantaneous, so spend can slightly exceed it. On Anthropic you can set a monthly limit on the organization and on each workspace except the Default Workspace. Set one anyway, after you've moved the key.

Does Abbi Labs check for this?

Yes. The free quick check on the home page (no sign-up) downloads your home page and the JavaScript files it loads from your own domain and looks for key patterns including OpenAI (sk-, sk-proj-), Anthropic (sk-ant-), Google API keys (AIza), Stripe secret keys, AWS access keys, GitHub tokens and Supabase service_role or sb_secret_ keys. It shows only a masked prefix and suffix, never the full key. It can't see keys that never reach the browser, and it can't tell whether your server route checks who's calling it.

Related guides

Weekly AI-app security checklist

One short email a week: a practical check for apps built with Supabase, Next.js and AI tools (RLS, leaked keys, headers and more). Unsubscribe anytime.