LEAKCADE

Your VITE_ and NEXT_PUBLIC_ variables are public

A .env file feels private, but the prefix decides who sees the value. Anything that starts with VITE_, NEXT_PUBLIC_, REACT_APP_ or EXPO_PUBLIC_ gets copied into the JavaScript bundle at build time, and every visitor downloads that bundle.

What the prefix does

The frameworks say so in their own docs. Vite: "VITE_* variables should not contain sensitive information such as API keys." Next.js: a NEXT_PUBLIC_ value "will be inlined into any JavaScript sent to the browser." Create React App: environment variables "are embedded into the build, meaning anyone can view them by inspecting your app's files." Expo: "Do not store sensitive info, such as private keys, in EXPO_PUBLIC_ variables."

Finding the key takes a minute. Open the site, open dev tools, search the loaded scripts for sk- or key.

If the build put a key in the bundle, every visitor has a copy. Renaming the variable and redeploying doesn't make the old key safe. Rotate it.

How it happens in AI-built apps

Server-only variables read as undefined in the browser, so the fetch fails. The quickest fix an AI assistant finds is to add the public prefix, or to put "use client" on the file that reads the secret. The error goes away and the key ships to the browser.

OpenAI's JavaScript SDK refuses to run in a browser until you set dangerouslyAllowBrowser: true. Its README says that option "exposes your secret API credentials in the client-side code." If you see that flag next to VITE_OPENAI_API_KEY, your key is public.

Which keys can live in the browser

Fine in client code: the Supabase anon or publishable key (Supabase's docs: anyone can read it, so it only reaches what Row Level Security allows), the Firebase web config, Stripe publishable keys (pk_), and Maps keys you've restricted to your domain.

Never in client code: OpenAI and Anthropic keys, Stripe secret keys (sk_live_), the Supabase service_role or secret key, database URLs with a password, and email API keys. Supabase's docs say the secret key bypasses Row Level Security, so it must never leave your control. Supabase is replacing the old anon and service_role keys with publishable and secret keys by the end of 2026. The rule stays the same.

How to fix it

  1. Rotate the key in the provider's dashboard. Old builds and deploys still contain the old value.
  2. Rename the variable without the public prefix, so only server code can read it.
  3. Move the call to the server: an API route, a server action or a Supabase Edge Function. The browser calls your route, and your route calls the provider.
  4. Rebuild, redeploy, then search the build output (dist/ or .next/static/) for the key prefix to confirm it's gone.

Fix prompt you can paste

Find every environment variable with a VITE_, NEXT_PUBLIC_, REACT_APP_ or
EXPO_PUBLIC_ prefix that holds a secret (API keys for OpenAI, Anthropic,
Stripe secret keys, Supabase service_role/secret key, database URLs, email
APIs). For each one: rename it without the public prefix, move the code that
uses it into a server route or server action, and call that route from the
client instead. Also check every "use client" file for server secrets.
Remove dangerouslyAllowBrowser. List every key I must rotate.

How Leakcade checks this

Our client-env check reads your repo as text. It flags secret-looking variables with a public prefix, in code and in committed .env files, as critical. It also flags server secrets and Supabase service-role clients inside "use client" files. Anon and publishable keys, Maps keys and plain URLs are left alone.

In a spot test on public GitHub files, the check flagged all 9 files that sent VITE_OPENAI_API_KEY to the browser and stayed quiet on 3 files that used the service role key correctly on the server.

Further reading

Sources

  1. Vite docs: Env variables and modes
  2. Next.js docs: Environment variables
  3. Create React App: Adding custom environment variables
  4. Expo docs: Environment variables
  5. Supabase docs: Understanding API keys
  6. OpenAI Node SDK README
  7. Leakcade: Leaked API keys in vibe-coded apps

Want a second pair of eyes?

We review your whole repo and write a fix prompt for every finding. $19, report within an hour.

Check my repo

Leakcade is an automated check. It can miss vulnerabilities and is not a substitute for a professional security audit or pentest. Provided as is. You stay responsible for your code. Where the law allows, our liability is limited to what you paid ($19). Terms

Patch Notes by Leakcade

A short weekly email: what changed in AI coding tools, one real security mistake, one prompt you can reuse.