// ANSWER

How do I check for exposed API keys?

Updated 2026-08-07 · 7 min read

The short answer

An exposed API key is a secret credential, like a password for a paid service, that anyone can read straight from your app or your code. You can check for one in a few minutes: view your site's source, watch the network tab, and search your JavaScript bundle for the tell-tale prefixes (sk_, service_role, and similar). Some keys, like Stripe's pk_ publishable key or Supabase's anon key, are meant to be public and are fine. If you find a true secret exposed, the fix is always the same: rotate it (generate a new one) and move the secret to server-side code where visitors cannot see it.

What an exposed API key actually is

An API key lets your app talk to a paid or private service, Stripe for payments, OpenAI for AI features, SendGrid for email, Supabase for your database. Some of these keys are public by design and some are secret. A public key only identifies your account; a secret key can spend money, read data, or send messages on your behalf.

"Exposed" means a secret key ended up somewhere a stranger can read it, in the browser, in your public code, or in your project's history. Once it is out, anyone can use it to run up your bill, drain your quota, or reach your data, and you will not get a warning first.

  • Safe to be public: Stripe publishable key (pk_...), Supabase anon key, Firebase config values. These are meant for the browser.
  • Must stay secret: Stripe secret key (sk_...), Supabase service_role key, OpenAI keys (sk-...), email/SMS provider keys, any database password.

Where AI tools leak them

AI coding tools leak secrets in a few predictable ways, usually because putting the key in the front-end is the shortest path to a working feature:

  • The NEXT_PUBLIC_ trap, in Next.js, any environment variable that starts with NEXT_PUBLIC_ is baked into the browser bundle on purpose. Put a secret there and it ships to every visitor. Secrets must NOT carry that prefix.
  • Hard-coded in front-end code, the key pasted directly into a component or script that runs in the browser.
  • A committed .env file, the file that holds your secrets accidentally pushed to a public GitHub repo.
  • Git history, even after you delete a key from a file, it usually still sits in the repo's history, viewable by anyone who clones it. Deleting is not enough; you must rotate.

Automated bots scan public code hosts for secret keys continuously, and leaked keys are often abused within minutes of being committed. [SOURCE NEEDED]

How to find yours (no coding required)

You can look with the same tools a curious stranger would use:

  1. View Source, open your live site, right-click, and choose "View Page Source." Use Ctrl+F to search the text for sk_, service_role, secret, and the names of services you use.
  2. The network tab, press F12, open the "Network" tab, reload, and inspect requests. Secrets sometimes travel in request headers or URLs where they should not.
  3. Search the JavaScript bundle, in developer tools, open the "Sources" tab, then use the global search (Ctrl+Shift+F) across all loaded scripts for the same prefixes. Your whole front-end is downloaded to every visitor, so anything in it is readable.
  4. Check your repository, if your code is on GitHub, search the repo (including older commits) for .env, sk_, and service_role.

Finding a pk_ or anon key here is normal. Finding an sk_, service_role, or provider secret is the problem.

If you find one: rotate, then move it server-side

Do these in order. Rotating first is critical, the moment a key is exposed, you must assume someone has copied it, so a new key is the only thing that truly closes the door.

  1. Rotate, in the service's dashboard (Stripe, Supabase, OpenAI, etc.), generate a new secret key and revoke the old one. This instantly kills any copy an attacker holds.
  2. Move it server-side, store the new secret in a server-only environment variable (no NEXT_PUBLIC_ prefix) and use it only from server code, such as a Next.js API route or route handler.
  3. Purge and re-check, remove the secret from front-end code, and remember that removing it from git history requires rewriting history, not just deleting the file.
Leaked in the browser vs. safe on the server (Next.js)
// EXPOSED: this runs in the browser, so the secret ships to every visitor.
// The NEXT_PUBLIC_ prefix forces it into the public bundle.
const stripe = new Stripe(process.env.NEXT_PUBLIC_STRIPE_SECRET_KEY);

// SAFE: keep the secret server-only (no NEXT_PUBLIC_ prefix) and use it
// inside a server route the browser never sees.
// file: app/api/checkout/route.ts  (runs on the server)
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

Which keys you can leave alone

Do not panic-rotate the public keys. Stripe's publishable key, Supabase's anon key, and Firebase config values are designed to sit in the browser, their protection comes from other layers (Stripe's server-side checks, Supabase RLS, Firebase security rules), not from being hidden. Rotating them will not improve security and may break your app. Focus your effort on the true secrets.

Key takeaways

  • An exposed API key is a secret anyone can read from your app, it can spend money or reach your data.
  • Public keys (pk_, Supabase anon, Firebase config) are meant to be visible; secrets (sk_, service_role, provider keys) are not.
  • AI tools leak secrets via the NEXT_PUBLIC_ prefix, hard-coded front-end code, committed .env files, and git history.
  • You can find leaks yourself with View Source, the network tab, and a search of the JavaScript bundle.
  • If a secret is exposed, rotate it first, then move it to server-only code, deleting the file alone does not undo the leak.

Frequently asked

Is it safe that my Stripe or Supabase key is visible?

If it is the publishable key (pk_) or the Supabase anon key, yes, those are built to be public. If it starts with sk_ or is the service_role key, no; that is a secret and must be rotated and moved server-side.

I deleted the key from my code. Am I safe now?

Not necessarily. If the key was ever committed to git, it usually still lives in the repository history where anyone can read it. The only reliable fix is to rotate the key so the old value stops working.

What does NEXT_PUBLIC_ mean and why does it matter?

In Next.js, the NEXT_PUBLIC_ prefix tells the framework to include that value in the browser bundle. It is meant for public values. Putting a secret behind that prefix ships the secret to every visitor, so secrets must never use it.

How would someone even find my key?

Mostly through automated scanning, not manual effort. Bots continuously crawl public code repositories and live sites for key patterns, so a leaked secret can be discovered and abused without anyone targeting you specifically.

Run a free security scan

Paste your app's link and get a plain-English A–F grade in about 60 seconds, plus the exact fix for every issue.

Free · No signup · Your code stays yours · Results in ~60s