30 October 2026 · 8 min read · BlitzHash Team · AI & Vibe Coding
OpenAI API key leaked? How secrets end up in your AI-built app's browser code
If your OpenAI API key leaked, the first job is to revoke it in the OpenAI dashboard and create a new one, then find out how it got out so the new key doesn't leak the same way. In apps built with Lovable, Bolt, Cursor, Replit or v0, the cause is usually the same: the key was placed in front-end code, so it was shipped to every visitor's browser inside your JavaScript. Anyone can open the page, read the code and copy it. The same thing happens with Stripe secret keys (sk_live_), Supabase secret or service_role keys, and email or SMS provider keys. This guide shows you how to check your own live app for exposed secrets in about 15 minutes, what to do if you find one, and how to set things up so it doesn't happen again.
If you want someone to check the whole app, including keys, access rules and payments, our Launch-Ready Check covers it and gives you a plain-English fix list. To look for yourself first, keep reading.
Why an OpenAI API key leaked from an AI-built app
Browsers can't keep secrets. Everything a web page needs to run, including its JavaScript, is downloaded to the visitor's machine. Build tools make this explicit:
- Vite (used by Lovable and Bolt projects) exposes any environment variable that starts with
VITE_to the browser code. Its docs warn that these variables end up in the client bundle and must not contain sensitive information. - Next.js exposes variables that start with
NEXT_PUBLIC_in the same way. Variables without the prefix stay on the server.
So when an AI builder hits an error like "API key is undefined" and fixes it by renaming OPENAI_API_KEY to VITE_OPENAI_API_KEY, the error disappears and the key is now public. The app works. Nothing warns you.
Other common routes:
1. Calling OpenAI straight from the browser. The generated code imports the OpenAI client into a React component, which means the key has to be in the browser. 2. Keys pasted into source code. A key typed directly into a file "just to test", then committed and published. 3. `.env` files committed to git. If the repo is public, or becomes public later, the key goes with it. 4. Source maps left on. Production source maps make the original code readable, comments and all. 5. Supabase secret keys used for convenience. When a query fails because of access rules, an AI tool may swap the public key for the service_role key, which bypasses every rule.
OpenAI's own guidance on key safety says not to deploy keys in client-side environments such as browsers or mobile apps, and to route requests through your own backend instead.
The 15-minute secrets-in-the-browser test
You need your live app and a desktop browser. You're only reading what your own app already sends to every visitor.
Step 1 (2 minutes): Open the app as a stranger
Open the live site in a private window, signed out. Open developer tools (F12 or right-click → Inspect) and go to the Sources tab (Chrome/Edge) or Debugger (Firefox). You'll see the JavaScript files the page downloaded, usually under assets/ with names like index-a1b2c3.js.
Step 2 (5 minutes): Search all files for key patterns
Press Ctrl+Shift+F (Cmd+Option+F on Mac) to search across every loaded file. Search for each of these, one at a time. Each line shows the pattern, what it usually is, and whether it belongs in the browser:
- `sk-` and `sk-proj-`: OpenAI secret key. In the browser? Never.
- `sk_live_`: Stripe live secret key. In the browser? Never.
- `rk_live_`: Stripe restricted key. In the browser? Never.
- `sb_secret_`: Supabase secret key. In the browser? Never.
- `service_role`: Supabase legacy admin key (or a mention of it). In the browser? Never as a key.
- `SG.`: SendGrid API key. In the browser? Never.
- `re_` near "resend": Resend API key. In the browser? Never.
- `pk_live_`: Stripe publishable key. In the browser? Yes, it's designed to be public.
- `sb_publishable_` or `anon`: Supabase publishable or legacy anon key. In the browser? Yes, if your access rules are right.
A match on sk- can be a false alarm (it's a common string), so look at the text around it. A real OpenAI key is a long string of letters and numbers right after the prefix.
Step 3 (2 minutes): Decode suspicious Supabase keys
Legacy Supabase keys are long strings starting with eyJ. Paste one into a JWT decoder you trust (or decode the middle section yourself) and look for "role":"service_role". If you see it, your admin key is public. A key showing "role":"anon" is the public key and is expected. We explain why in is the Supabase anon key safe in the browser?.
Step 4 (2 minutes): Check network requests
Go to the Network tab and use the app normally: send a chat message, generate something, check out. Look for requests going straight to api.openai.com, api.stripe.com or an email provider. A request from the browser to api.openai.com with an Authorization: Bearer sk-... header means the key is in the browser, even if Step 2 missed it.
Step 5 (2 minutes): Look for source maps and leftovers
In the Sources tab, check whether you can see your original file names and folder structure (like src/components/Chat.tsx). If you can, production source maps are public. Also try yourdomain.com/.env and yourdomain.com/.git/HEAD in the address bar. Both should return "not found".
Step 6 (2 minutes): Check the repo
If the code is on GitHub, search the repository for the same patterns, and check whether .env is listed in .gitignore. GitHub's secret scanning can alert on many provider key formats; on public repositories it runs automatically, and for private ones it depends on your plan and settings.
Prefer not to dig through developer tools? Our free leak check reads your public page and scripts and flags Supabase service or secret keys sitting in public code, plus a few related warning signs. It doesn't sign in or keep row data.
Found a key? Do this, in this order
1. Revoke and replace it at the provider. For OpenAI, delete the key in the dashboard and create a new one. For Stripe, roll the secret key. For Supabase, follow the rotate-a-leaked-key steps in the API keys docs. Revoke first: deleting the code doesn't help, because copies already exist. 2. Check usage and logs. Look at the provider's usage page for activity you don't recognise. For Supabase, check whether data could have been read or changed with the admin key. 3. Move the call to the server. Put the API call in a backend route, a Supabase Edge Function or a serverless function. The browser talks to your function; only your function holds the key. 4. Add limits. Put per-user rate limits and a sign-in requirement on the function so a stranger can't use your server as a free proxy to the AI provider. Set usage limits and alerts at the provider. 5. Remove the key from history. Deleting it from the latest commit leaves it in git history. Since the key is revoked, history is less urgent, but clean it if the repo is public. 6. Redeploy and re-test. Repeat Steps 2 to 5 on the new build.
Supabase's newer secret keys help here: its docs say a secret key doesn't work in a browser, because Supabase checks the request and returns 401 Unauthorized. That's a useful safety net, not a fix. The docs also note an attacker can still use a leaked key from other tools.
How to stop secrets leaking again
- One rule for your AI builder. Add to your project instructions: "Never put secret keys in front-end code or in variables starting with
VITE_orNEXT_PUBLIC_. Call paid APIs only from server functions." - Name variables so mistakes look wrong. Public values get the public prefix; secrets never do. If you ever see
VITE_OPENAI_KEY, stop. - Keep secrets in the platform's secret store. Lovable has a Secrets feature for this, and Supabase Edge Functions and Vercel have their own environment settings.
- Turn off public source maps in production unless you upload them privately to an error tracker.
- Re-run the test after big AI sessions. The leak usually appears while "fixing" an error.
For the wider pre-launch picture, our Lovable security checklist before you take payments covers access rules, webhooks and admin roles alongside secrets. And the free launch-ready self-check gives you a quick overall read.
Practical takeaways
- Anything in front-end code is public. Prefixes like
VITE_andNEXT_PUBLIC_make variables public on purpose. - Search your live JavaScript for
sk-,sk_live_,sb_secret_andservice_role. - If a key leaked, revoke it first, then fix the code.
- Paid APIs belong behind your own server function with sign-in and rate limits.
- Publishable keys are meant to be public; secret keys never are.
FAQ
What happens if my OpenAI API key is leaked?
Anyone who has it can make requests billed to your account until you revoke it. Revoke the key, create a new one, check your usage for unfamiliar activity, and move the calls to a server function so the new key stays private.
Can I hide an API key in React or Vite with environment variables?
No. Environment variables used in front-end code are written into the JavaScript at build time and shipped to every visitor. They keep keys out of your source files, but not out of the browser.
Is the Supabase anon key a leaked secret?
No. The anon or publishable key is designed to be public, and your access rules decide what it can reach. The service_role and sb_secret_ keys are the dangerous ones, because they bypass those rules.
How do I safely use OpenAI in a Lovable or Bolt app?
Call OpenAI from a backend function (for example a Supabase Edge Function) that holds the key as a secret, require the user to be signed in, and limit how often each user can call it.
---
Found a key, or not sure whether what you found matters? Our Launch-Ready Check reviews secrets, access rules, payments and backups, and gives you a ranked fix list in plain English. Book a free technical assessment and we'll tell you what a stranger can already see in your app.
Sources
- OpenAI: Best practices for API key safety
- Vite: Env variables and modes
- Next.js: Environment variables
- Supabase: API keys
- Stripe: API keys
- GitHub: About secret scanning
- Lovable: Secrets
