secrets · July 10, 2026 · 6 min read
API Keys in Your JavaScript Bundle: How to Find and Fix Them
Anything in your frontend JavaScript is public. Learn which API keys are safe to ship, which never are, and how to find leaks in your built bundle.
Your backend code is private. Your frontend code is not — every byte of JavaScript your site ships can be downloaded and searched by anyone, no login required. If an API key is in there, it’s public, and automated scrapers are much faster at finding it than you are.
Your frontend is public by definition
Every visitor’s browser downloads your bundle to run it. Anyone can open DevTools, view source, or fetch the files with a script. Minification is not protection: it renames variables, not string values, so a key sits in the file verbatim and a simple pattern match finds it instantly. Bots crawl the web doing exactly that, at scale, around the clock.
How keys end up in the bundle
- Build-time inlining. Frameworks replace environment-variable references with literal strings at build time. Server-side env vars stay on the server — but anything referenced in client code gets pasted into the shipped JavaScript.
- The NEXT_PUBLIC_ trap. In Next.js, only variables prefixed
NEXT_PUBLIC_reach the browser. That prefix is a promise: “this value is safe to publish.” The classic failure: client code crashes becauseprocess.env.OPENAI_API_KEYis undefined in the browser, and someone — a developer or an AI assistant — “fixes” it by renaming the variable toNEXT_PUBLIC_OPENAI_API_KEY. The build succeeds, the app works, and the key is now public. Vite’sVITE_and Create React App’sREACT_APP_prefixes behave the same way. - Hardcoded keys.Pasted directly into code “temporarily” during prototyping, then shipped.
- Config objects. Firebase configs, Supabase client setups, and similar objects mix values that are fine to publish with slots where the wrong key type is catastrophic.
AI coding tools optimize for “it runs,” which is often the shortest path — not the safest one. If your app came out of Cursor, Lovable, Bolt, v0, or Replit, an explicit bundle audit belongs on your ship checklist.
Which keys are safe to ship — and which never are
Some keys are designed to be public. They identify your project, and the real protection lives server-side:
- Stripe publishable key (
pk_live_...) - Supabase anon key — safe only when Row Level Security is enabled
- Firebase web API key — safe only with proper security rules
- Analytics and maps keys, ideally domain-restricted
These must never appear in a browser:
- Stripe secret key (
sk_live_...) - Supabase service_role key
- OpenAI and Anthropic API keys
- AWS access key IDs and secrets
- Private keys, SMTP and SendGrid credentials, GitHub tokens
Rule of thumb: if a key can spend money, read other users’ data, or act with admin power, it belongs on a server. Full stop.
What a leaked key actually costs
This isn’t theoretical. A leaked OpenAI key can rack up thousands of dollars in fraudulent usage before you notice, because harvested keys get used within hours — often for resale. Leaked AWS credentials routinely end in crypto-mining bills. Beyond the invoice: your quota gets exhausted (real users see errors), your provider may suspend the account, and whatever data the key could read should be considered read.
How to check: search your bundle, not your source
Your source can look clean while your bundle isn’t — inlining happens at build time. So check the built output:
# search your build output (.next/, dist/, build/)
grep -rE "sk-[A-Za-z0-9]|sk_live_|AKIA[0-9A-Z]{16}|-----BEGIN|service_role" \
.next/static dist build 2>/dev/nullThen check the deployed reality: open DevTools → Sources on your live site and search the JavaScript for sk-, sk_live_, AKIA, and service_role.
Finally, review what you’ve opted into publishing:
grep -h "NEXT_PUBLIC_\|VITE_\|REACT_APP_" .env* 2>/dev/nullFor each line, ask: would I post this value on a billboard? If not, it needs a new home. And make sure the .env file itself isn’t downloadable — that’s its own critical issue.
How to fix a leaked key
- Rotate first, before touching code. Once a secret has shipped, treat it as stolen. Old bundles live on in CDN caches, archives, and downloads — revoke and reissue now.
- Move the call server-side. A small proxy route keeps the key on the server:
Your browser code calls// app/api/chat/route.ts — runs on the server; the key never ships export async function POST(req: Request): Promise<Response> { const res = await fetch("https://api.openai.com/v1/chat/completions", { method: "POST", headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}`, // no NEXT_PUBLIC_ "Content-Type": "application/json", }, body: JSON.stringify(await req.json()), }); return Response.json(await res.json()); }/api/chatinstead of the provider. Add auth and rate limiting to the route so strangers can’t use your proxy for free. - Un-prefix. Anything secret loses its
NEXT_PUBLIC_/VITE_/REACT_APP_prefix and moves behind a route like the one above. - Set spend limits and usage alerts with every provider that offers them — they turn a disaster into an email.
- Restrict the public keys you do ship with domain allowlists where supported.
How BoringSec checks this
BoringSec’s bundle-secrets scanner downloads the JavaScript your site actually ships and runs 21 secret detectors against it: Supabase anon and service_role keys and project URLs, Firebase API keys and config, Stripe secret and publishable keys, OpenAI keys in both formats, AWS access key IDs and secrets, private keys, tokens for GitHub, Slack, Anthropic, Google OAuth, SendGrid, Hugging Face, Twilio, and Mailgun, plus generic API-key patterns.
It’s the highest-weighted category of the Boring Score — our score for AI-built apps— because a shipped secret does more damage than any missing header. Like every base URL module, it’s evidence-first: findings ship with captured proof and cite their standard (OWASP, CWE, RFC, MDN). Details on /methodology.
FAQ
Is my Supabase anon key in the bundle a leak?
Does minifying or obfuscating my code hide keys?
I deleted the key from my code. Am I done?
Keep reading
Check your site now
One scan applies the relevant checks from 20 base URL modules plus 3 verified-owner background engines. Free, no signup, with base results appearing first.
Scan your site free