Anticode Open builder

Why you should never put API keys in frontend code, and where they go instead

Anything the browser downloads, every visitor can read: the HTML, the JavaScript bundle, the network requests. A secret key placed there is public the moment the page is live, however it was minified, encoded or tucked into an environment variable. Keep secret keys on a server, have the browser call your server, and treat any key that has been shipped to a browser as already leaked.

Updated · By the Anticode team

Why hiding doesn't work

The short answer to why you should never put API keys in frontend code is that a front end is code you send to strangers to run on their machines. Browser developer tools show every file and every request with its headers, and automated scanners crawl public sites and code repositories looking for key patterns. Minifying, splitting a key into pieces or base64-encoding it slows nobody down. Environment variables don't help either if the build puts them in the bundle: Next.js, for example, inlines any variable prefixed NEXT_PUBLIC_ into the JavaScript sent to browsers, as its environment variable guide explains, and other build tools have similar prefixes.

Secret keys and public keys

Which keys can appear in a browser
KindExamplesIn the browser?
Secret or server keyStripe secret keys (sk_live_), OpenAI and Anthropic API keys, AWS access keys, database URLs with passwords, SMTP passwordsNever
Publishable keyStripe publishable keys (pk_live_), some maps keysYes, as designed, ideally restricted to your domain
Row-level-security-protected client keySome backend-as-a-service anonymous keysOnly if access rules on the server are set correctly

The rule of thumb: if the provider's documentation calls it secret, or if it can spend money, send email or read data without further checks, it belongs on a server.

Why AI-built front ends leak them

Asked for "a page that calls the weather API" or "a chatbot using GPT", a model will often write the shortest working version: a fetch from the browser with the key in a header. It runs in the preview, so it looks finished. The model doesn't know where the code will be deployed or that the page will be public. Prototypes built this way frequently go live unchanged, which is how keys in AI-generated code end up being scraped and billed.

Find secrets in a live site

  1. Open the site, then View Source and search for patterns such as sk-, sk_live_, AKIA, api_key, apikey, secret, token and Bearer.
  2. Open DevTools, Sources panel, and search across all loaded files (Ctrl+Shift+F, or Cmd+Option+F on a Mac) for the same patterns.
  3. Open the Network panel, use the site's features, and look at request headers and URLs for keys sent to third-party APIs.
  4. Search the repository and its history, not just the current files. GitHub's secret scanning runs automatically on public repositories.
  5. Check build output folders such as dist, build and .next/static, where inlined variables end up.

Fix it: move the call to a server

  1. Revoke or rotate the exposed key first, in the provider's dashboard. Removing it from the code doesn't un-publish it; old copies live in caches, archives and scrapers' databases. The OWASP secrets management cheat sheet puts revocation before everything else.
  2. Create a small server endpoint on your own domain, such as a route handler or serverless function, that holds the new key in a server environment variable.
  3. Have the browser call your endpoint, not the provider. The endpoint checks the request, calls the provider with the key and returns only what the page needs.
  4. Limit what the endpoint does: fixed operations rather than a pass-through, input validation, rate limits per visitor, and a spending cap at the provider.
  5. Restrict the key itself where the provider allows: by IP, domain, permission scope or budget.
  6. Check the logs and bill for use you don't recognise during the time the key was public.

How Anticode handles this

Sentinel, Anticode's security check, scans the exact HTML that ships for anything that looks like a secret, and a page that fails can't be published. Design-engine pages contain no scripts at all, so there's no front-end code to hold a key. App builds run in a sandbox with no network of any kind, so they can't call an outside API with a key; anything that would need one is simulated with labelled sample data. When a form genuinely needs your own logic, a site function makes a signed HTTPS call from Anticode's server to an endpoint you run, carrying an HMAC anticode-signature header, so your keys stay on your server. Sentinel matches known patterns; it can't recognise every provider's format, so still read the code you add elsewhere.

Mistakes to avoid

  • Deleting the key from the code without rotating it.
  • A proxy endpoint that forwards anything, which turns your server into a free relay for whoever finds it.
  • Committing .env files to the repository. Keep them in .gitignore and set production values in the host's settings.
  • Trusting "private" repositories as storage for keys. Access changes, forks happen and repositories get made public.

Questions people ask

Can I hide an API key with environment variables in React or Vite?

Not if the variable is bundled for the browser. Prefixed variables such as NEXT_PUBLIC_ or VITE_ end up in the JavaScript visitors download. Only server-side code keeps a variable private.

Is a publishable key safe in the browser?

Yes, that's what it's for, but restrict it to your domains where the provider allows, and never confuse it with the matching secret key.

My key was exposed for an hour. Do I still need to rotate it?

Yes. Automated scanners find keys within minutes. Rotate it and check usage for that hour.

Sources

  1. How to use environment variables in Next.js (Next.js docs), checked
  2. Secrets Management Cheat Sheet (OWASP), checked
  3. About secret scanning (GitHub Docs), checked