FlowCheck
All guides

Exposed API keys in apps built with AI coding tools

How a publishable key differs from a secret, why the client bundle is a public place, and what to do after a key has already shipped.

A publishable key is meant to be in the browser. Stripe documents keys that start with pk_ as publishable. A Supabase anon key is the public client key, and row level security is what keeps it from being a copy of your database. Everything else you paste into a component to make a demo run is a candidate for a leak.

What counts as a secret

  • Stripe secret keys start with sk_. Restricted keys start with rk_. A restricted key has a smaller API scope. It is still a secret.
  • A Supabase service_role key bypasses row level security. It belongs in a server environment variable.
  • OpenAI keys, GitHub personal access tokens, and database URLs with passwords are secrets even when a coding agent dropped them into a React file.

Prefixes that intentionally ship to the browser

Vite exposes variables prefixed with VITE_ to client code. Next.js inlines variables prefixed with NEXT_PUBLIC_ into the browser bundle at build time. Both are the documented way to pass a public value to the client. Neither prefix makes a secret safe. If the browser needs the value to run, anyone who loads the page can read it.

Deleting the line from the current source does not retire a key that was already downloaded, and it does not remove the value from git history. Rotate the key in the provider dashboard, store the replacement where only the server can read it, and redeploy. If the repository was public, treat the old key as compromised.

Stripe: API keys

Vite: environment variables and modes

Next.js: environment variables

GitHub: removing sensitive data from a repository