Guides / Incident response

An exposed API key is a live incident. Move in this order.

Automated scanners watch public repos and websites for credentials around the clock. There are well-documented cases of leaked LLM keys being abused within hours, with the bill arriving before the founder even knew. If a key of yours may be public, this is the order of operations.

How keys escape vibe-coded apps.

The usual routes, roughly in order of frequency: bundled into the client because a build-time variable was prefixed VITE_ or NEXT_PUBLIC_, which by design inlines it into public JavaScript; committed to a repo that later went public, where deleting the file in a newer commit changes nothing because history keeps it; pasted into AI chat transcripts, screenshots, or screen recordings; and hardcoded server URLs that turned out to be client-reachable.

The window between a key appearing in public and being tried by someone else's script is minutes, not days. Which is why the response order below starts at the provider, not in your codebase.

The first hour.

  1. Revoke at the provider, immediately. Create a new key, switch the server to it, and revoke the old one. Revoke means delete or disable at the provider, not remove from your code. Until the provider refuses the old key, the leak is live.
  2. Audit usage and cap the damage. Check the provider's usage and billing pages for calls you did not make. Set hard spending limits and alerts while you are there; every serious provider supports them, and they turn a catastrophic bill into a bounded one.
  3. Purge it from git history if it was committed. Removing the line is not enough; the key lives in every prior commit. Rewrite history with git filter-repo (or BFG), force-push, and tell collaborators to re-clone. If the repo was ever public, assume the key was already harvested and let the rotation do the real work.
  4. Move the call server-side. If the key was in the browser, rotation alone fixes nothing: the new key will ship in the next bundle. Provider keys belong in server code only. The section below shows the standard fix.
  5. Restrict the replacement key. Scope it to the minimum permissions, and apply IP or referrer restrictions where the provider supports them. A key that can only do one thing from one place is a much smaller prize.

The proxy pattern: the browser never holds the key.

The client calls your endpoint; your endpoint holds the key in an environment variable, applies your own auth and rate limit, and forwards the request. A minimal API route version:

// /api/generate - runs on the server. The key lives in env, never in the bundle.
export async function POST(req) {
  const user = await requireUser(req);            // your auth check
  await enforceRateLimit(user.id);                // your rate limit

  const { prompt } = await req.json();
  const res = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: {
      "x-api-key": process.env.ANTHROPIC_API_KEY, // server-side env var
      "anthropic-version": "2023-06-01",
      "content-type": "application/json",
    },
    body: JSON.stringify({
      model: "claude-sonnet-5",
      max_tokens: 1024,
      messages: [{ role: "user", content: prompt }],
    }),
  });
  return new Response(res.body, { status: res.status });
}

The two lines that make this production-grade are the auth check and the rate limit. A proxy without them is an open relay: attackers do not need your key if your endpoint will spend your money for anyone who asks. This exact pattern works as a Next.js route, a Supabase Edge Function, or a Cloudflare Worker.

Prevention that sticks.

  • Turn on secret scanning and push protection in your repo host; GitHub's blocks a detected key before the commit lands. Add gitleaks to CI for depth.
  • Keep separate keys per environment, so a leaked dev key never touches production quota.
  • Treat the VITE_ and NEXT_PUBLIC_ prefixes as a contract: they mean "this will be public". Nothing with that prefix should ever be a secret.
  • Set spending alerts on every provider today, while nothing is wrong. They are the smoke detector for this whole class of incident.

Questions

Questions founders ask.

Is deleting the key from my code enough?

No. If the key was ever public, in a bundle, a commit, or a paste, assume it was captured. Git history retains it in every prior commit, and scrapers archive public pages. The only real fix is revoking the key at the provider and issuing a restricted replacement that lives server-side.

The key was only exposed for a few minutes. Am I fine?

Do not bet on it. Credential scanners monitor public commits and deployments continuously, and documented abuse has begun within minutes of exposure. Rotation takes five minutes; treat it as automatic rather than assessing the odds each time.

Can I put an AI provider key in the frontend if I restrict it?

For OpenAI and Anthropic keys, no; they carry no domain restriction, so anything in the browser is fully usable by anyone who extracts it. Only keys explicitly designed to be publishable, like Stripe's publishable key or the Firebase config, belong client-side. Everything else goes behind the proxy pattern.

What do the VITE_ and NEXT_PUBLIC_ prefixes actually do?

They tell the bundler to inline that variable into the public JavaScript at build time. That is the feature, not a bug: it is how client code gets config. The corollary is that these prefixes are a promise that the value is safe to publish. A secret with that prefix is already leaked.

My provider bill already shows abuse. What now?

Revoke the key, then contact the provider's support with the timeline; providers have processes for abuse-driven charges and sometimes credit them. Preserve the usage logs, check whether the same leak exposed anything else such as database credentials, and close the route it escaped through, or it will happen again with the new key.

After the fire drill

One leaked key usually means other gaps. Find them first.

Keys leak from apps whose secrets, access rules, and deployment hygiene were never reviewed. A production-readiness review checks all of it at once and hands you a ranked fix list, before the next incident does it for you.