If your code reads an API key through a variable starting with VITE_, NEXT_PUBLIC_ or REACT_APP_, that key is in your public JavaScript and anyone can copy it. An environment variable keeps it out of your Git repo, not out of the browser: the build tool pastes the value into the bundle.
Why the prefix matters
-
Vite (Lovable, Bolt):
VITE_variables are exposed in client code, and the Vite docs say they shouldn't hold secrets. -
Next.js (v0, many Cursor projects):
NEXT_PUBLIC_variables are inlined into the browser JavaScript at build time. -
Create React App: same for
REACT_APP_.
Hardcoding a key in a component is the same problem. If the browser runs the code, the browser has the key.
Safe in the browser vs secret
| Usually fine in frontend code | Never in frontend code |
|---|---|
Supabase anon / sb_publishable_ key (with RLS) |
Supabase service_role / sb_secret_
|
Stripe pk_live_...
|
Stripe sk_live_... or rk_...
|
| Firebase web config | Firebase Admin service account JSON |
| Map keys restricted by domain | OpenAI, Anthropic, Gemini, Resend, Twilio, AWS keys, database URLs |
Check your live site in two minutes
- Open your live site, then DevTools (F12) > Sources. Press Ctrl/Cmd+Shift+F to search all files.
- Search for:
sk-,sk_live,rk_live,service_role,sb_secret_,AKIA,re_,SG.,-----BEGIN. - In the Network tab: if the browser calls
api.openai.comdirectly, the key is in the browser. - Locally, after
npm run build, searchdist/or.next/for the same strings.
The fix: call the API from the server
The browser calls your own endpoint (an API route, serverless function or Supabase Edge Function), and that endpoint calls the third-party API with a key from a server-only env var. Add a login check and a rate limit so it doesn't become a free proxy.
My [SERVICE] API key is exposed in my frontend (read from [VARIABLE] in [FILE]). Please: 1) create a server-side endpoint that reads the key from a server-only env var with no public prefix, 2) remove the key and the prefixed variable from all client code, 3) make the frontend call the new endpoint, require a logged-in user and add a per-user rate limit, 4) show me the diff and where to set the env var on my host.
Already leaked? In this order
- Rotate the key in the provider's dashboard.
- Set a spending limit or usage alert.
- Check the provider's usage logs.
- Deploy the server-side fix.
- If it was ever committed to Git, treat it as public even after deleting the file.
Free pre-launch checklist (14 checks to run before you share your link): https://milokits.com/checklist?ref=devto
We make Milo's Launch Check, a passive outside-in check for AI-built apps that also looks for keys in your bundle. Launch week: Quick Check $19 instead of $29 until 1 November (code QUICK19): https://milokits.com/?ref=devto#pricing
Top comments (0)