Here is the rule that prevents most API key leaks: anything your frontend can use, a visitor can read. Minifying the code does not hide it, and neither does putting the key in an environment variable or splitting it into pieces. If the browser needs the key to make a request, the key is in the JavaScript, the Network tab, or both, and anyone can copy it in under a minute. Bots scan public websites and GitHub for keys all day, and a leaked AI or payment key can turn into a large bill or a data breach within hours.
This guide explains how keys end up in frontend code, which keys are safe to ship and which are not, how to check your own site, and what to do if you find one.
How secret keys end up in the browser
Most frameworks copy environment variables with a certain prefix into the JavaScript they send to the browser, at build time:
- Next.js: variables starting with
NEXT_PUBLIC_ - Vite (and the many apps built on it):
VITE_ - Create React App:
REACT_APP_ - Expo / React Native:
EXPO_PUBLIC_
The prefix exists so that you choose, on purpose, what becomes public. The leak happens when a secret gets the prefix anyway, usually because the code calls an API from the browser and the build complained that the variable was undefined. AI coding tools do this a lot: calling OpenAI or Stripe straight from a React component is the shortest way to make a feature work in the preview.
Keys also leak through files that should never be public: a .env file served by the web server, source maps that include server code, or a public Git repository with a committed .env in its history.
Which keys are fine in the browser
Some keys are designed to be public. They identify your project, and the real protection lives somewhere else:
- Supabase anon or
sb_publishable_keys, as long as Row Level Security protects every table (see our Supabase RLS guide). - Firebase web config, including its
apiKey, as long as your Security Rules are strict (see our Firebase rules guide). Restrict the key to your domains and consider App Check. - Stripe publishable keys starting with
pk_live_orpk_test_. - Google Maps browser keys, restricted by HTTP referrer to your own domains.
- Analytics IDs and site keys for CAPTCHAs such as reCAPTCHA or Turnstile.
Which keys must never be in the browser
- Stripe secret keys (
sk_live_) and restricted keys (rk_live_) - OpenAI keys (
sk-andsk-proj-) and keys of other AI providers - Supabase service_role or
sb_secret_keys, which bypass all your database rules - AWS access keys (
AKIAfollowed by 16 characters) and their secret keys - SendGrid (
SG.), Mailgun, Twilio and other email or SMS keys - GitHub tokens (
ghp_,github_pat_) and other deployment tokens - Database connection strings with passwords, JWT signing secrets and webhook signing secrets (
whsec_)
How to check your own site
In the browser. Open your live site, press F12, go to Sources and press Ctrl+Shift+F (Cmd+Option+F on a Mac) to search every loaded file. Search for sk_live, sk-proj, sk-, service_role, AKIA, secret and token. Then use the app normally with the Network tab open: a request from the browser directly to api.openai.com, api.stripe.com or similar means your secret key is on the page.
In your build output. After building, search the files that get deployed:
grep -rEo "sk_live_[0-9A-Za-z]{10,}|sk-proj-[0-9A-Za-z_-]{20,}|AKIA[0-9A-Z]{16}|sb_secret_[0-9A-Za-z_-]{10,}" \
.next/static dist build 2>/dev/null
In your Git history. A key you deleted from the code is still in every earlier commit. Search for each prefix you use:
git log --all -p -S "sk_live_" -- .
Tools such as gitleaks and trufflehog scan a whole repository history for hundreds of key formats, and GitHub's secret scanning can block pushes that contain keys.
From the outside. Our free Website Health Check reads the JavaScript your homepage loads, the way a browser does, and looks for common secret-key formats, along with security headers and outdated libraries.
Found one? Fix it in this order
1. Rotate the key first. Revoke the leaked key in the provider's dashboard and create a new one. Assume the old key has already been copied: removing it from your code does nothing for the copies. Check the provider's usage and billing pages for activity you do not recognise.
2. Move the call to the server. The browser calls your own endpoint, and your endpoint calls the provider with the key from a server-only environment variable (no public prefix). In Next.js, for example:
// pages/api/summarize.js: runs on the server, so the key never reaches the browser
export default async function handler(req, res) {
if (req.method !== "POST") return res.status(405).end();
const user = await getSignedInUser(req); // your own session check
if (!user) return res.status(401).json({ error: "Please sign in" });
const response = 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: process.env.AI_MODEL,
messages: [{ role: "user", content: String(req.body.text || "").slice(0, 4000) }],
}),
});
res.status(response.status).json(await response.json());
}
The same pattern works with a Supabase Edge Function, a Firebase Cloud Function or any small serverless function.
3. Protect the new endpoint. A server route that calls an AI provider for anyone who asks is the same leak with extra steps. Require a signed-in user, limit how often each user and IP address can call it, cap the input size, and set a monthly spending limit with the provider.
4. Clean up. Remove the old variable everywhere, rebuild and redeploy, and run the checks above again. If the key was in a public repository, rewriting the history is optional once the key is rotated, but do it if the repository will stay public.
Prevent the next leak
- Never give a secret a public prefix. If the build says it is undefined in the browser, that is the framework protecting you.
- Keep
.envfiles in.gitignore, and keep secrets in your host's environment settings. - Use separate keys for development and production, with the least access each one needs.
- Turn on secret scanning in your repository and spending alerts with every paid API.
Need help?
Exposed keys are one of the first things we check in our AI app security audit, along with database rules, access between users, admin routes and payments. You get a plain-English report with proof and fixes, and we can make the fixes for you by pull request. If your app was built with an AI coding tool, our pre-launch security checklist covers the rest of the common problems.



