Everything in your front-end bundle is public. If a secret key is in it, anyone who opens the page can copy it and use it as you. With an AI provider key that means unlimited requests billed to your account.
Secret keys and public keys
Some keys are meant to be in the browser. Reporting them would be a false alarm, so SecScan does not.
| Public by design (not a finding) | Secret (a finding) |
|---|---|
Stripe pk_live_, pk_test_ | Stripe sk_live_, rk_live_ |
| Supabase anon or publishable key | Supabase service_role key |
Firebase AIza… web API key | Cloud or service-account private keys |
| Google Maps browser key (restrict by referrer) | OpenAI sk-…, Anthropic sk-ant-…, AWS AKIA…, GitHub ghp_… |
How it happens
- A build variable with a public prefix (
VITE_,NEXT_PUBLIC_,REACT_APP_) holds a secret. Anything with that prefix is compiled into the bundle. - An AI tool calls the provider straight from the browser because that is the shortest code.
- A key was pasted into a component while testing and never removed.
What to do
- Revoke the key now at the provider, then create a new one. Removing it from the code is not enough: it is already in the page you served and in any cache.
- Move the call to a server route (for example
/api/chat) that adds the key from a server-side environment variable. The browser talks only to your route. - Check the route is safe to expose: require sign-in and rate-limit it, or anyone can spend your credit through it.
- Rescan to confirm.
SecScan also flags the design even when no key is visible: an AI provider called directly from the browser, or a system prompt shipped in the bundle.
← Is my Lovable, Bolt or Cursor app secure? What to check Firebase security rules left open (Firestore, Realtime Database, Storage) →
Last reviewed 2026-09-26