An OpenAI or Anthropic key in your frontend is someone else's free compute
You wired up the chat feature over a weekend. The model call worked on the first try, the demo looked great, and you shipped. The fastest way to get the request working was to call the API straight from the browser, so that's what the code does, and the key it uses starts with sk-proj- or sk-ant-.
That key is now in the JavaScript bundle every visitor downloads. It is your real, billable API key, and it is sitting in plain text behind View Source. The question is not whether someone can read it. They can. The question is what it costs you when they do.
How the key gets to the browser in the first place
Calling an LLM API needs a secret key in the Authorization header. There is no public, browser-safe version of an OpenAI or Anthropic key the way there is for Stripe (pk_live_) or Google Maps. Every key is a secret key.
So when a tutorial, a no-code builder, or an AI assistant writes "call the API from the client," the key has to ride along. It ends up hardcoded in a component, or pulled from an env var that got prefixed with NEXT_PUBLIC_ or VITE_ and therefore baked into the client build. The moment it's prefixed for the browser, the bundler inlines the literal string into the JavaScript. Minification doesn't hide it. The key is a fixed prefix followed by a long random tail, which is exactly the shape a text search finds in a second.
What a stranger does with it
The blunt version: they bill you. An LLM key has no spending cap unless you set one, and most people don't until the first surprise invoice.
A leaked key gets scraped by the same bots that crawl for AWS keys, and it ends up on a list. From there it's free inference. People run their own product on your key, resell access to it, or just burn it generating data at scale. With a project key the blast radius can be wider than the bill: depending on how the key is scoped, it may read your fine-tuned models, your uploaded files, and your assistants, which can include the prompts and data you trained on. The first signal you get is usage you can't explain, a rate limit you didn't hit on purpose, or a bill that's ten times last month's. There's no alert that says "a stranger is using your key." There's just the number going up.
The pattern under the leak
The mistake isn't the key. It's the architecture that puts a secret in a place secrets can't survive. Any code path that needs an LLM key has to run on a server you control, never in the browser.
That's the rule whether you use OpenAI, Anthropic, Mistral, Cohere, or a router like OpenRouter. The browser talks to your backend; your backend holds the key and talks to the model. It costs you one small API route and gives you the place to add the things a raw client call never has: per-user rate limits, a spend cap, abuse detection, and a prompt you don't want to hand to the public. If you've also dropped any other secret prefix into the bundle, the same fix covers all of them, because the fix is "secrets live server-side," not "this particular key is special."
The fix, and how to confirm it
Move the call behind your own endpoint, then rotate the exposed key, because a key that was ever public stays compromised even after you stop using it.
// client component, ships to every visitor
const res = await fetch("https://api.openai.com/v1/chat/completions", {
headers: { Authorization: "Bearer sk-proj-aBcD...wXyZ" },
// ^ this string is in the JS bundle, readable by anyone
body: JSON.stringify({ model, messages }),
});Then set a hard monthly spend limit in the provider dashboard regardless, because it's the backstop that turns a leak from a catastrophe into a bad afternoon. After rotating, generate a fresh key, revoke the old one, and confirm nothing in the build references it.
Whether a live sk-proj-, sk-ant-, or bare sk- string is sitting in your shipped JavaScript is something you can read from outside without any access to your account, because it's in the file you serve to the public. That's the read SurfaceCheckr does: it fetches your pages and linked scripts, scans them for the key prefixes the major providers use, and tells you if one is exposed and on which page. It can't see your provider bill or whether the key has already been abused, only that it's readable, which is the part you can fix before the bill arrives.
Find it before someone else does.
Paste your domain. The grade and issue count are free, and you'll see in a couple of minutes exactly what's reachable from outside.