Guide

Is your Lovable or Bolt app leaking API keys?

If your app was built with Lovable, Bolt, v0, Replit or a similar tool, everything that runs in the browser is public. That includes every JavaScript file your app ships. If a secret key ended up in one of those files, anyone who opens your site can copy it. They don't need to hack anything. They just read it.

This guide shows you how to check your own app in about ten minutes, which keys are fine to be public, which are not, and what to do if you find one.

Why keys end up in the browser

Most AI builders produce a React app built with Vite or Next.js. Both tools copy certain environment variables into the browser bundle on purpose:

  • Vite (Lovable, Bolt and most React starters) exposes any variable that starts with VITE_. If you have VITE_OPENAI_API_KEY in your .env, it is in your JavaScript.
  • Next.js (v0 and many Cursor projects) exposes anything starting with NEXT_PUBLIC_.
  • Create React App exposes anything starting with REACT_APP_.

The prefix is a label that means "this is safe to publish." The problem is that AI assistants sometimes add the prefix to make an error go away, or paste a key straight into a component file. The app works, so nobody notices.

The other common pattern is calling an AI provider directly from the browser. If your frontend sends requests to api.openai.com or api.anthropic.com, the key has to be in the browser for that to work.

Step 1: Look at network traffic

This is the fastest check.

  1. Open your live app in Chrome, Edge or Firefox.
  2. Open DevTools (F12, or right click and choose Inspect) and go to the Network tab.
  3. Use the feature that calls AI, sends email, takes payment or talks to any third-party service.
  4. Look at where the requests go. Requests to your own domain, to your Supabase project, or to a serverless function are normal. Requests that go straight from the browser to api.openai.com, api.anthropic.com, generativelanguage.googleapis.com, api.stripe.com or api.resend.com are a red flag.
  5. Click a suspicious request and open the Headers tab. An Authorization: Bearer sk-... or x-api-key header means the key is sitting in your frontend.

Step 2: Search the bundle itself

Network traffic only shows features you clicked. To find keys in code paths you didn't trigger, search the JavaScript files directly.

In the browser: open DevTools, go to the Sources tab, and press Ctrl+Shift+F (Cmd+Option+F on Mac) to search across every loaded file. Search for each of these one at a time:

Search forWhat it usually isSafe in the browser?
sk-proj-, sk-OpenAI secret keyNo
sk-ant-Anthropic keyNo
sk_live_, rk_live_Stripe secret or restricted keyNo
pk_live_Stripe publishable keyYes, by design
service_role, sb_secret_Supabase service key (bypasses all row level security)No, never
anon JWT, sb_publishable_Supabase anon or publishable keyYes, if your RLS policies are right
AIzaGoogle API key (Firebase, Maps, Gemini)Depends on restrictions, see below
re_Resend keyNo
gsk_, r8_, xai-Groq, Replicate, xAI keysNo
ghp_, github_pat_GitHub tokenNo
AKIAAWS access key ID (the secret is usually nearby)No

Also search for the words apiKey, secret, token and Bearer. Short matches like sk- will give false positives in minified code. You're looking for a long random string right after the prefix.

From a terminal: if you prefer, download your app's scripts and grep them. Replace the URL with your own app:

curl -s https://your-app.example.com/ | grep -oE 'src="[^"]+\.js"'
curl -s https://your-app.example.com/assets/index-abc123.js \
  | grep -oE '(sk-(proj-|ant-)?[A-Za-z0-9_-]{20,}|sk_live_[A-Za-z0-9]{20,}|sb_secret_[A-Za-z0-9_-]{20,}|AIza[0-9A-Za-z_-]{35})'

Vite apps often split code into many chunks, so repeat for each .js file listed in the Sources tab, not just the first one.

Step 3: Check which Supabase key you shipped

Lovable and Bolt apps almost always use Supabase, and the Supabase key in the browser is expected. The question is which one. Older Supabase keys are JWTs: three chunks of text separated by dots, starting with eyJ. Copy the middle chunk and decode it with any base64 decoder, or paste the whole token into a JWT debugger that runs locally. Look at the role field.

  • "role": "anon" is the public key. Fine to ship, as long as row level security is set up properly. That part is where most real leaks happen, so read our Supabase RLS checklist.
  • "role": "service_role" is the admin key. It ignores every RLS policy. Anyone who has it can read, change and delete your entire database. Treat this as an emergency.

Newer Supabase projects use sb_publishable_... (public) and sb_secret_... (server only) keys, which are easier to tell apart.

Step 4: Google keys need a closer look

Keys starting with AIza are used for Firebase, Google Maps and Gemini. A Firebase web config key is meant to be in the browser. But a Google API key works for every API enabled on its Google Cloud project unless you restrict it. If the same project also has the Gemini API (Generative Language API) turned on, a "harmless" Firebase key can be used to run up AI charges on your account.

In the Google Cloud console, open APIs & Services, then Credentials, click the key and check two things: Application restrictions (limit it to your website's domain) and API restrictions (limit it to the APIs the browser actually needs). Keep Gemini on a separate key that only lives on your server.

Step 5: Check for public source maps

Source maps (.map files) let anyone see your original, unminified source code, including comments and file names. They make finding secrets much easier. Open one of your JS files and add .map to the end of the URL. If you get a big JSON file back instead of a 404, your source maps are public. In Vite, build.sourcemap is off by default, so if they're on, something turned it on. Either set it back to false or use 'hidden' and upload maps privately to your error tracker.

Step 6: Check your repo history

If your project is synced to GitHub, a key that was ever committed is still in the history even after you delete the line. Tools like gitleaks or TruffleHog scan the full history of your own repo. Make sure .env is listed in .gitignore. GitHub's secret scanning and push protection are worth turning on too.

Found a key? Do this, in this order

  1. Rotate it first. Go to the provider's dashboard, create a new key and revoke the old one. Removing the key from your code does nothing for copies people already took. Bots scrape public sites and GitHub for keys within minutes.
  2. Check usage. Look at the provider's usage or billing page for spikes you don't recognize. If you see abuse, contact their support. Some will help with fraudulent charges if you report it quickly.
  3. Move the call to a server. The browser should call your own backend, and the backend calls the provider with the key stored as a server-side secret. In a Lovable or Bolt app with Supabase, that usually means a Supabase Edge Function with the key added under Edge Function secrets. On Vercel or Netlify, use a serverless function and an environment variable without the public prefix.
  4. Remove the public prefix. Rename VITE_OPENAI_API_KEY to something like OPENAI_API_KEY and only read it in server code.
  5. Redeploy and search again. Repeat Step 2 on the live site to confirm the new build no longer contains the key.

A prompt that works well with most AI builders: "Move every call to OpenAI (or Stripe, Resend, etc.) out of the frontend into a server-side function. Store the API key as a server secret, never with a VITE_ or NEXT_PUBLIC_ prefix. The frontend should only call our own function, and the function should check that the user is logged in."

Once the key is on a server, the next risk is people abusing your endpoint to burn credits. Our guide on protecting AI features from surprise bills covers rate limits and spend caps.

FAQ

Is the Supabase anon key safe to have in my frontend?

Yes. The anon key (or the newer sb_publishable_ key) is designed to be public. Your row level security policies are what protect the data. The service_role key or sb_secret_ key must never be in the browser.

Is a Firebase API key a secret?

A Firebase web config key is meant to be public, but Google API keys work for every API enabled on the project unless restricted. Restrict the key to your domain and the APIs the browser needs, and keep Gemini on a separate server-only key.

I deleted the key from my code. Am I safe now?

No. Anyone who copied it can still use it. Rotate the key at the provider first, then move the call to a server and redeploy.