ShipWatchFree scan

API key exposed in your frontend? Here's what to do

If an API key is in your frontend code, it's public, no matter how it got there or how hard it looks to find. The good news is the fix is well understood.

The problem: "hidden in the bundle" is not hidden

Modern frontends get compiled into JavaScript files that every visitor downloads. Minification makes the code ugly, but it doesn't hide anything. Any string in that code, including an API key, can be found with the browser's built-in developer tools in under a minute.

Build tools are explicit about this. Vite's environment variable docs say variables prefixed with VITE_ are exposed in client-side code and shouldn't contain secrets. Next.js's docs explain that NEXT_PUBLIC_ variables are inlined into the JavaScript sent to the browser. AI builders lean on these prefixes because they make things "just work", which is exactly how secrets slip through.

What can someone do with a leaked key? That depends on the key:

Not every key in a frontend is a leak. Publishable keys (Stripe pk_, Supabase anon/sb_publishable_, most analytics IDs) are designed to be public. The skill is telling the two apart.

How to check your app yourself

In the browser

  1. Open your live site (the deployed version, not localhost) and open developer tools.
  2. Go to Sources and search all files (Ctrl/Cmd+Shift+F). Try: sk_live_, rk_live_, sk-, sk-ant-, AKIA, sb_secret_, service_role, secret, apiKey, Bearer.
  3. In the Network tab, reload and click through your app. Look at request headers going to third parties. If your browser is sending Authorization: Bearer sk-... straight to an AI provider, that key is public.

In your code

# every env var that your bundler will publish
grep -rnE "(VITE_|NEXT_PUBLIC_|REACT_APP_|EXPO_PUBLIC_)[A-Z_]*(SECRET|KEY|TOKEN|PASSWORD)" \
  --include=*.ts --include=*.tsx --include=*.js --include=*.jsx --include=.env* .

Each match deserves a look: is this value really safe for the public?

In your git history

If a key was ever committed, it's in your history even after you delete it. If your repo is on GitHub, secret scanning can alert you to many known key formats.

With the free scan

ShipWatch fetches your page and up to 25 of its JavaScript files and checks them for Stripe live keys, OpenAI and Anthropic keys, AWS access key IDs, Google API keys and Supabase secret keys. It shows a masked preview so the report itself doesn't leak the key again.

How to fix it

The fix always has the same shape: the browser calls your server, and your server calls the API with the key. The key lives in your host's secret storage and never reaches the bundle.

There is a secret API key in my frontend code. Move every call that uses it into a server-side endpoint (an API route, serverless function or Supabase Edge Function). Store the key as a server-only environment variable without any VITE_, NEXT_PUBLIC_ or REACT_APP_ prefix. The frontend should call my endpoint, and the endpoint should check the user is signed in before calling the third-party API. Show me every file you changed.
My app calls the OpenAI (or Anthropic) API directly from the browser. Create a server endpoint that proxies these requests: it should require a signed-in user, limit how many requests each user can make per minute, cap the maximum tokens, and only allow the specific model and prompt format my app uses, so the endpoint can't be used as a free general-purpose proxy.
List every environment variable my app uses and label each one "public (safe in browser)" or "secret (server only)". Rename any secret that has a public prefix, and update the code so secrets are only read in server-side code.

Then rotate. This step isn't optional.

Changing the code doesn't revoke the old key. Anyone who already copied it can keep using it. In each provider's dashboard, create a new key, put it in your server's environment, deploy, then revoke the old one. For Google keys you intend to keep in the browser, add HTTP referrer and API restrictions instead.

Check for misuse

Look at the provider's usage and billing pages for activity you don't recognize, and set usage limits or billing alerts where the provider offers them.

Make it hard to happen again

Scan your app free in 30 seconds

Paste your live URL. ShipWatch checks your page and JavaScript for leaked keys (including Supabase service_role), exposed .env and .git, missing security headers and HTTPS redirects, and gives you a plain-English report with fix prompts. Passive and read-only, no signup.

Run the free scan