There's one rule about websites that explains a lot of security incidents: everything your site sends to the browser belongs to whoever is using the browser.
That includes your JavaScript files. They get compressed and renamed during the build, so they look unreadable, but every string inside them is still there. If one of those strings is a secret API key, anyone who opens your site can copy it and use it. On your account, on your bill.
AI coding tools put keys in the frontend more often than you'd hope. It's rarely malicious. When you ask for "a button that summarises this text with GPT", the fastest working answer is to call the AI provider directly from the page, key included. The feature works in the preview, you ship it, and the key ships with it.
Keys that are fine, keys that aren't
Not every key in your frontend is a problem. Some services issue keys designed to be public, and they rely on other protections.
Fine in the browser:
- A Supabase anon or publishable key, protected by Row Level Security on your tables
- A Stripe publishable key, starting
pk_live_orpk_test_ - A Firebase web config (the
apiKeythere identifies your project and is protected by Firebase Security Rules) - A Google Maps key restricted to your own domain
Never fine in the browser:
- Anything starting
sk-(OpenAI and several other AI providers) orsk_live_(Stripe secret keys) - A Supabase
service_roleor secret key, which ignores every database rule - Email provider keys (Resend, SendGrid, Postmark), which let anyone send mail as you
- Any key whose name includes secret, private, admin or server
When in doubt, check the provider's docs for whether the key is "publishable" or "secret". They almost always say.
How to check your live site in five minutes
Do this on the deployed site, not your code editor. What matters is what actually reached the browser.
- Open your site in Chrome and sign in, so the app loads the parts only users see.
- Press F12 (or right-click and choose Inspect), then open the Sources tab.
- Press Ctrl+Shift+F (Cmd+Option+F on a Mac) to search across every file the page loaded.
- Search one at a time for:
sk-,sk_live_,service_role,secret,private_key,Bearer, and the names of the paid services you use. - Then open the Network tab, use your app's features, and click through a few requests. Look at the request headers for anything labelled
Authorizationorx-api-keygoing to a third party (likeapi.openai.com) directly from the browser.
If a request goes from the browser straight to an AI or email provider with a key attached, that key is public, even if it never appears as readable text in the source.
The environment variable trap
A common response is "but I put it in an environment variable". Environment variables protect secrets only when they're read on a server.
Modern frameworks copy some variables into the browser code on purpose, so the frontend can use things like your public Supabase URL. Which ones depends on a prefix. In Next.js, anything starting NEXT_PUBLIC_ is written into the JavaScript at build time. In Vite, which Lovable and Bolt use, it's anything starting VITE_. Vite's own documentation warns that these variables should not contain sensitive information for exactly this reason.
So NEXT_PUBLIC_OPENAI_KEY or VITE_STRIPE_SECRET is a secret published to every visitor, just with extra steps.
If you find one
Do these in order.
First, move the call to the server. Ask your AI tool for a server route (or a Supabase Edge Function) that holds the key and makes the call, and have the page call your route instead. Ask for that route to require a signed-in user and to limit how often each user can call it, otherwise you've moved the problem rather than fixed it.
Second, revoke the old key and create a new one. This is the step people skip. Moving the key doesn't un-publish it. Anyone who visited your site while it was exposed may have a copy, and so may any bot that scrapes sites for keys. The Moltbook exposure started with a key found in exactly this kind of JavaScript file.
Third, check the provider's usage dashboard for activity you don't recognise, and set a spending limit if the provider offers one.
Why this keeps happening at scale
GitGuardian, which scans public code for leaked credentials, counted 28.65 million new hardcoded secrets pushed to public GitHub in 2025, up 34% on the year before. Secrets for AI services alone rose 81%. Their report also found that 64% of valid secrets leaked in 2022 were still usable in January 2026, because nobody revoked them.
That's the pattern to break. Finding a key is uncomfortable. Finding it and not revoking it is how an embarrassing week turns into a bill. Our pre-launch checklist covers the rest of the secrets section, including keys you pasted into an AI chat.