How to fix an exposed API key in a Lovable app
If a secret key appears in your app's browser JavaScript, anyone can copy it and use it, which can run up charges on your OpenAI or Stripe account or let them read your database. Here's how to find it and fix it.
1. Know which keys are safe to expose
- Safe in the browser: the Supabase
anon/ publishable key (only if row-level security is on), Stripe publishable keys (pk_live_…), and public analytics IDs. - Never in the browser: the Supabase
service_role/ secret key, Stripe secret keys (sk_live_…,rk_live_…), OpenAI keys (sk-…), Resend, Twilio and other provider secrets.
Note: any variable prefixed VITE_ or NEXT_PUBLIC_ is bundled into the browser code. Prefixing a secret that way exposes it.
2. Check whether a key is exposed
- Open your live site, then DevTools → Sources (or view the page's JS files) and search for
sk-,sk_live,service_roleandeyJ(JWTs). - In DevTools → Network, watch requests: a call going straight from the browser to
api.openai.commeans the key is in the browser. - Search your repo, including history:
git log -p | grep -E "sk-|sk_live|service_role", or run a scanner like gitleaks.
3. Rotate the key first
Deleting the key from your code is not enough: it's already public, and it stays in git history. Create a new key and revoke the old one in the provider's dashboard (OpenAI → API keys; Stripe → Developers → API keys → Roll key; Supabase → Project Settings → API keys). Then check the provider's usage and billing pages for activity you don't recognize.
4. Move the call server-side
In Lovable apps backed by Supabase, the usual fix is a Supabase Edge Function that holds the secret and makes the API call for the browser:
- Store the secret: in Supabase → Edge Functions → Secrets (or
supabase secrets set OPENAI_API_KEY=…). In Lovable, use its secrets / Supabase integration rather than pasting the key into code. - Have the function read it with
Deno.env.get("OPENAI_API_KEY")and call the provider. - Have the frontend call the function with
supabase.functions.invoke("your-function"). - Require a signed-in user in the function (verify the JWT, which is on by default), and add a rate limit so nobody can drain your credits through it.
A prompt you can paste into Lovable:
Move all calls to the OpenAI API out of the frontend into a Supabase Edge Function. Read the key from the OPENAI_API_KEY secret with Deno.env.get, never from client code. Require an authenticated user, and limit each user to 20 requests per hour. Remove every hard-coded key and any VITE_ variable that holds a secret.
5. Verify
- Redeploy, then search the live JS bundle again for the old and new key prefixes.
- Confirm the old key is revoked: requests using it should fail.
- If the key was committed, it's still in git history. Rotation makes it useless; rewriting history is optional.
Want a second pair of eyes?
LaunchLatch checks your app for this and the other common vibe-coding mistakes, with a report in 24 hours. The first 5 beta audits are free.
Get a free audit →Last updated October 16, 2026.