Why your OpenAI or Anthropic API key ends up in your frontend bundle — and how to find it first
Short answer
An LLM API key ends up in a frontend bundle when the app calls OpenAI, Anthropic or another provider directly from the browser, usually through an environment variable with a client prefix such as VITE_, NEXT_PUBLIC_ or EXPO_PUBLIC_. Build tools inline those variables into JavaScript that every visitor downloads, so the key is public the moment the site deploys. Find it by searching the built bundle for key prefixes; fix it by moving the call into a server or Edge Function that authenticates the user, rate-limits them and holds the key as a server-only secret — then rotate the leaked key.
Last updated · Sekrd Research
- Vite exposes only variables prefixed
VITE_to client code — and it exposes all of them. - Next.js inlines
NEXT_PUBLIC_variables into the JavaScript sent to the browser; Expo does the same forEXPO_PUBLIC_. - OpenAI’s key-safety guidance: never deploy your API key in client-side environments such as browsers or mobile apps.
- Sekrd dataset (799 hosts, Mar–Sep 2026): high-severity secret leaks of any kind on 2.1% of hosts.
How the leak happens
The fastest way for an AI coding tool to make a chat feature work is to call the provider SDK from the component that renders the chat. The SDK needs a key, the tool reads it from an environment variable, and to make that variable visible in the browser it gives it the client prefix. The app works in preview. The key is now part of the production bundle.
// Leaks: the key is inlined into the shipped JavaScript
const client = new OpenAI({
apiKey: import.meta.env.VITE_OPENAI_API_KEY,
dangerouslyAllowBrowser: true,
});
The OpenAI SDK even requires an explicit dangerouslyAllowBrowser flag for this; assistants add it to make the error go away.
Find it before someone else does
Search what you actually ship, not your repository:
# after a production build
grep -rEo "sk-(proj-)?[A-Za-z0-9_-]{20,}|sk-ant-[A-Za-z0-9_-]{20,}|AIza[0-9A-Za-z_-]{35}" dist/ .next/static/ build/ 2>/dev/null
Also open your deployed site, view the loaded JavaScript in the browser’s developer tools and search for sk-. If you can see it, so can anyone.
The fix: move the call server-side
- Create a server route or Edge Function that receives the prompt.
- Require a signed-in user (verify the JWT) — or, for public features, a CAPTCHA token.
- Rate-limit per user, and cap tokens per request.
- Store the provider key as a server-only secret; remove the client-prefixed variable entirely.
- Rotate the leaked key in the provider dashboard. Deleting it from code does not unpublish it: it was served to every visitor and may be cached.
// supabase/functions/chat/index.ts — the key stays on the server
const key = Deno.env.get("OPENAI_API_KEY"); // set with: supabase secrets set OPENAI_API_KEY=...
Other keys that leak the same way
- Supabase secret / service_role keys — full database access, bypasses RLS.
- Stripe secret keys (
sk_live_) — the publishablepk_live_key is fine in clients, the secret key never is. - Telegram bot tokens and generic “admin” API tokens — Sekrd’s dataset found both in client code.
Frequently asked questions
Is it safe to call the OpenAI API from the browser?
No. Any key used in the browser is downloadable by every visitor. OpenAI’s guidance is to never deploy API keys in client-side environments; route calls through a server or Edge Function that holds the key.
Are VITE_ environment variables secret?
No. Vite exposes every variable prefixed with VITE_ to client code, which means it is inlined into the JavaScript bundle. The same applies to NEXT_PUBLIC_ in Next.js and EXPO_PUBLIC_ in Expo.
What should I do if my API key was in my frontend?
Rotate it immediately in the provider dashboard, move the API call to a server-side function that authenticates and rate-limits users, and remove the client-prefixed variable. Removing the key from code alone is not enough because it was already served publicly.
Sources
Don't ship until you're sekrd
Run a free scan to find the vulnerabilities your AI missed.
Scan Your App Free