Symptoms
- You can see keys in the browser developer tools or page source
- Your API bill went up for no clear reason
- Your code calls paid services directly from the browser
- Secret keys are stored in variables that start with VITE_ or NEXT_PUBLIC_
Why it happens
Code that runs in the browser is public. Anyone can open the developer tools and read it, keys included.
AI tools often put the key right next to the code that uses it, because that makes the demo work. Some keys are made to be public, like a Supabase anon key or a Stripe publishable key. Secret keys are not. People run scripts that scan websites looking for them.
Try this first
-
Replace the exposed keys first
Go to each service, create a new key and delete the old one. Do this before anything else. The old key may already be copied.
-
Move the call to a server function
Create a small server function that holds the key and talks to the service. Your app calls your function instead of the service. Supabase Edge Functions, Netlify Functions and Next.js route handlers all work. Here is a Next.js example:
// app/api/chat/route.ts export async function POST(req: Request) { const { message } = await req.json() const res = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.OPENAI_API_KEY}`, }, body: JSON.stringify({ model: 'your-model-name', messages: [{ role: 'user', content: message }], }), }) return Response.json(await res.json()) } -
Store the key without a public prefix
Call it OPENAI_API_KEY, not VITE_OPENAI_API_KEY or NEXT_PUBLIC_OPENAI_API_KEY. Those prefixes send the value to the browser.
-
Set spending limits
Most paid APIs let you set a monthly limit or an alert. Turn it on, so a future leak cannot turn into a huge bill.
-
Check your git history
If a key was ever saved in a public repository, treat it as leaked, even if you deleted the file later. The old version is still in the history.
When to stop and call a person
Get help if you are not sure which keys are secret, if many parts of the app call paid services, or if you need limits so one user cannot spend your whole budget. Setting up a proper server layer is a common first job in an app cleanup.
Questions people ask
Is my Supabase anon key a problem?
No. It is meant to be public, as long as Row Level Security is turned on for your tables.
Is a Stripe publishable key a problem?
No, that one is public by design. Your Stripe secret key is the one that must stay on the server.
Related fixes
Lovable app login works in preview but fails on your domain
People can log in on the Lovable preview link, but not on your real domain. This is almost always a setting in…
Anyone can read your Lovable app database
If Row Level Security is off on your Supabase tables, anyone who has your public key can read or change your data…
Bolt app works in the editor but the deploy fails
Your Bolt project runs fine inside Bolt, but the build fails on Netlify or another host. Most of the time it is…
Every fix Cursor makes breaks something else
You ask the AI to fix one bug and two new ones show up. This loop is very common. The way out…