← Learn

A leaked API key or token in your frontend: what to do

If a secret key or token from any provider ships to the browser, anyone can read it and use it on your account. Here is how to tell a real leak from a public-by-design key, and how to fix it.

AI builders love to put configuration in the frontend, and sometimes that includes a secret — a key or token that was meant to stay on a server. Once it ships to the browser, it is in everyone's "view source"; treat it as public.

Real leak vs public-by-design

Not every key in your bundle is a problem. Public by design: a Supabase anon key, a Firebase apiKey, a Stripe publishable pk_ key, analytics IDs. A real leak: anything labelled secret, service_role, sk_/sk-, an auth-session secret, a database connection string, or a provider token (SendGrid, Resend, Groq, Turso, Lemon Squeezy, and the like). Shipshape only raises an alarm when the shape matches a real secret, not a public key.

The fix (same for every provider)

  1. Rotate it now. Assume it is compromised — generate a new one in the provider dashboard and revoke the old one.
  2. Move it server-side. Put the secret in a server environment variable and call the provider from an API route / Edge Function, never from client code.
  3. Stop prefixing secrets. Anything in a VITE_, NEXT_PUBLIC_, or REACT_APP_ variable is shipped to the browser — secrets must not use those.
  4. Re-scan to confirm the key is gone from the deployed bundle.

→ Scan your app free

FAQ

How do I know if a key in my frontend is actually dangerous?
Public keys (Supabase anon, Firebase apiKey, Stripe pk_) are safe by design. Secret keys/tokens (service_role, sk_, provider secrets, connection strings) are real leaks. Shipshape only flags the latter.

Do I really have to rotate the key, or just remove it?
Rotate it. Once a secret has shipped to the browser you must assume someone copied it, so removing it is not enough — generate a new one and revoke the old.

Where should secrets live instead?
In server-side environment variables, used only from an API route or serverless/Edge function — never in client code or a public-prefixed env var.

Related questions

Check your own app
Free passive scan, ~10 seconds, no login.