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)
- Rotate it now. Assume it is compromised — generate a new one in the provider dashboard and revoke the old one.
- 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.
- Stop prefixing secrets. Anything in a
VITE_,NEXT_PUBLIC_, orREACT_APP_variable is shipped to the browser — secrets must not use those. - Re-scan to confirm the key is gone from the deployed bundle.
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
- Missing security headers on your app: what they do and how to add them
- CORS misconfiguration: when "Access-Control-Allow-Origin: *" is dangerous
- Exposed source maps: your original source code is downloadable
- Unvalidated postMessage: a cross-window message handler without an origin check