How to Find Exposed API Keys in Your Frontend

Secret keys in your client bundle are public to anyone with devtools. Here is how to find leaked API keys in your frontend and what to rotate first.

By Artem Vysotskyi · Updated

Anything shipped to the browser, NEXT_PUBLIC_ variables, hardcoded keys, source maps, a committed .env file, is readable by anyone who opens devtools or views your page source. There is no client-side hiding place. The fix is to find what is already exposed, move real secrets server-side, and rotate the keys that leaked.

Why frontend keys are public by design

Anything that runs in the browser is downloadable. Your JS bundle, your .env-derived build output, your inline script tags, all of it is just text a visitor's browser fetches and executes. There is no sandbox that keeps a string secret once it ships to the client, no matter how it is obfuscated or minified.

This is not a bug you can code around. It is why the entire model of "public" versus "secret" keys exists: some keys are designed to be seen (an anon key, a publishable key), and some are designed to never leave your server. The mistake is treating a secret key as if shipping it to the frontend were a configuration detail instead of a leak.

Safe vs unsafe keys

Most platforms give you two classes of key, and mixing them up is the single most common leak LeakRank sees.

Safe to expose (public, publishable, anon):

  • Stripe's pk_ publishable key
  • Supabase's anon key
  • Firebase's apiKey (an identifier, not a secret)
  • Any key explicitly documented as "safe for client-side use"

Never expose (secret, private, service-role):

  • Stripe's sk_ secret key
  • Supabase's service_role key
  • Any third-party API key without a documented public/private split (assume it is private by default)

The prefix or naming convention exists precisely so you can tell them apart at a glance. When a platform does not clearly label a key, treat it as secret until proven otherwise.

How to find leaked keys

Browser devtools. Open your live site, open devtools, and check the Sources and Network tabs. Search the loaded JS for key, secret, token, and any provider prefixes you use (sk_, service_role, etc.). If it is visible here, it is visible to everyone. While the Network tab is open, the document request also shows the response headers your deployment returns, which is a separate check with its own security headers check. Headers will not save a leaked key, but they are the other half of what every visitor receives.

Search the JS bundle directly. Fetch your built bundle and grep it:

curl -s https://yourapp.com/_next/static/chunks/*.js | grep -oE '(sk_live_|service_role)[A-Za-z0-9._-]+'

This catches keys that are technically minified but not actually hidden, minification changes variable names, not string contents.

Check source maps in production. If source maps are deployed alongside your build, anyone can reconstruct readable source from the minified bundle, including any secret that was inlined at build time rather than fetched at request time. Confirm .map files are not publicly reachable in production.

Check .env in the repo. Run git log --all -- .env and grep your full history, not just the current tree, for key-shaped strings. A .env committed once and later deleted is still in git history and still exposed to anyone with repo access, including a public GitHub repo indexed by scrapers.

Move secrets server-side

The structural fix is to never let a secret key exist in code that ships to the browser. Concretely:

  • Call third-party APIs that require a secret key from a server route or Edge Function, not from client-side code.
  • In Next.js, only variables prefixed NEXT_PUBLIC_ are exposed to the browser, everything else stays server-only, but only if you actually read it in server code. A secret key stored without the prefix but still referenced inside a client component gets bundled anyway.
  • Proxy the request: client calls your server, your server calls the third party with the secret attached, the response comes back without the key ever touching the browser.

Rotate the keys you leaked

Finding a leaked key is not the fix, rotating it is. Once a secret key has been in a public bundle, a public repo, or a source map, assume it has been copied. You have no way to know who has it or how long they have had it.

For each leaked key: generate a new one in the provider's dashboard, update your server environment variables, redeploy, then revoke the old key. Check the provider's usage or audit logs for the affected window for activity you cannot explain.

Prevent it next time

  • Add a pre-commit check that greps staged files for known secret prefixes before they can be committed.
  • Keep a documented list of which keys on your team are public-safe and which are not, so new engineers do not have to guess.
  • Re-run a check like this after every dependency or SDK upgrade, some SDKs default to inlining config in ways that change between versions.

Scan your live URL for exposed keys, free

FAQ

I found an API key in my JavaScript bundle. Is that a problem?

It depends which key. Publishable keys are meant to ship to the browser: Stripe's pk_ key, Supabase's anon key, Firebase's apiKey, which is an identifier rather than a secret. Secret keys are not: Stripe's sk_ key, Supabase's service_role key, and any third-party key with no documented public and private split. If it is one of those, treat it as leaked, move the call server-side and rotate it.

Which keys are safe to put in my frontend and which are not?

Safe: anything a platform documents as publishable, public or safe for client-side use. Never safe: a secret, private or service-role key, and any API key that does not document a public and private split, which you should assume is private until proven otherwise. The prefix or naming convention exists so you can tell the two apart at a glance.

How do I check whether my deployed site is leaking a secret key?

Open the live site in devtools and search the loaded JS for key, secret, token and the provider prefixes you use. Fetch your built bundle and grep it for sk_live_ or service_role. Confirm no .map source map files are publicly reachable. Then run git log --all on .env, because a file committed once and later deleted is still in your history.

Can I hide an API key in my frontend code?

No. Obfuscation, minification and encoding do not hide a string from anyone who opens devtools or downloads your bundle, because minification changes variable names, not string contents. If a key has to stay secret it cannot exist in code that reaches the browser. Call the third party from a server route or Edge Function and proxy the response back.

The key was only exposed for a few minutes. Do I still need to rotate it?

Yes. There is no way to confirm nobody copied it, and scrapers index public repos and deployed bundles continuously, often within minutes. Generate a new key, update your server environment variables, redeploy, then revoke the old one. Check the provider's usage or audit logs for the exposure window for traffic you did not generate.

Find out in 30 seconds

Paste your URL and LeakRank shows what a stranger can actually read from your app. Free, no signup.

Scan my app free →

Related guides