Secrets · 7 min · Updated 26 Aug 2026
Exposed API keys in your JavaScript bundle: find them before an attacker does
Anything in your client bundle is public. Here is how to tell whether a secret key is sitting in yours, and how to fix it.
Everything your browser downloads to run your app is readable by anyone who visits it. Your JavaScript bundle is not hidden, not compiled away and not protected by minification. If a secret is in it, treat it as already public.
A key is safe in the client only if it is designed to be public: a Supabase anon key, a Stripe publishable key, a public analytics token. A secret key, a service-role key or a private API token in the bundle is a direct line into your data or your bill.
Why coding agents ship keys to the client
An agent's job is to make the feature work. Calling an API from the browser is the shortest path, and pasting the key that makes the call succeed is the shortest path to that. The result runs perfectly in a demo and exposes the key to every visitor.
The second cause is a naming convention. In Next.js, a variable prefixed NEXT_PUBLIC_ is deliberately shipped to the browser; in Vite it is VITE_. Put a secret behind that prefix and the framework will faithfully publish it.
Find a leaked key in your own bundle
You do not need any tools. Open your live app, open your browser's developer tools, and use the Sources or Network tab to search the loaded JavaScript for the patterns keys follow.
sk_live_ Stripe secret key — must never be client-side sk_test_ Stripe secret test key — still should not ship service_role Supabase service-role JWT — bypasses all RLS AKIA… AWS access key id — root of cloud access AIza… Google API key — often over-scoped -----BEGIN a private key of any kind — never in a bundle
A Supabase anon key (a long JWT) and a Stripe key starting pk_ are meant to be public and are fine to find. It is the sk_, the service_role and anything labelled secret or private that matters.
If you find one, rotate before you refactor
The instinct is to move the key server-side and redeploy. Do that second. Do this first: assume the key is compromised and rotate it, because it has been downloadable for as long as the app has been live, and a leaked key is not un-leaked by deleting the line.
- 01Rotate the key in the provider's dashboard so the exposed value stops working.
- 02Move the real call server-side: an API route, an edge function or a serverless function that holds the key in a server-only environment variable.
- 03Confirm the variable is not prefixed
NEXT_PUBLIC_orVITE_, so the framework keeps it on the server. - 04Redeploy, then re-open the bundle and confirm the key is gone from it.
- 05Check the provider's usage logs for calls you did not make while the key was exposed.
A fix here is easy to believe and easy to get wrong — the key can linger in a cached bundle or a second entry point. The only way to know is to search the live bundle again after deploy and confirm the exact string no longer appears.
What a Preflight sees here
This exposure is fully visible from outside, without a login, which is what makes it a good first thing to check. The free Preflight fetches your live bundle and source maps, scans them for secret key patterns, and quotes the exact line and the key type back to you with the time it was pulled.
If nothing sensitive is in the bundle, that is worth confirming too. The report says plainly what it checked and what it could not see from outside, so a clean secrets result is a real result, not a silence.