Short answer: Any API key that ends up in your front-end bundle is public, because anyone can open the browser's developer tools and read it. Find leaked keys with a secret scanner such as Gitleaks, move them behind a server-side function, and rotate every exposed key right away, even if you are not sure anyone saw it.
Why front-end keys are public
Everything your app sends to the browser can be read by the person using it: JavaScript files, source maps, network requests and environment values baked in at build time. Hiding a key in a variable name or minifying the bundle does not change that.
Build tools make this easy to miss. The Vite documentation states that variables prefixed with VITE_ "will be exposed in client-side source code after Vite bundling," and that they "should not contain sensitive information such as API keys." If your app is built on Vite, the prefix is tempting because it makes a value work in the browser immediately. That convenience is the leak.
Wiz Research reported finding third-party API keys and service account credentials hardcoded into client-side JavaScript of vibe-coded apps. The problem is growing: GitGuardian counted 1,275,105 leaked secrets tied to AI services on public GitHub in 2025, up 81% from 2024, and leaked Supabase keys rose 992%, according to The Hacker News.
Which keys can live in the browser
Not every key is a secret. Some are designed to be public and are protected by other controls.
| Key type | Can it be in front-end code? | What protects it |
|---|---|---|
Stripe publishable key (pk_...) | Yes | Designed for client-side use, per Stripe |
Stripe secret or restricted key (sk_..., rk_...) | No | Must stay on a server |
| Supabase anon/publishable key | Yes | Row-level security policies |
| Supabase service role key | No | Bypasses row-level security entirely |
| OpenAI, Anthropic and other model API keys | No | Billing is tied to the key |
| Maps or analytics keys with domain restrictions | Usually | Referrer or domain allowlists |
| Database passwords, SMTP credentials, webhook signing secrets | No | Must stay on a server |
Stripe's key guidance is explicit: secret keys "must stay in your server environment," and client-side apps should embed publishable keys instead. The Supabase anon key is only acceptable in the browser if row-level security is on for every table. See The Supabase RLS mistake.
How to find exposed keys
Check three places: your repository, your git history and your live site.
1. Scan the repository and its history. Gitleaks is a free, MIT-licensed scanner that checks the working tree and full git history for API keys, access credentials and passwords:
gitleaks git -v .
History matters. A key you deleted last month is still readable in old commits, and on a public repo it may already have been copied.
2. Search the built bundle. Run your production build, then search the output folder for common key prefixes:
grep -rE "sk_live|sk_test|sk-|service_role|AKIA" dist/
3. Look at the live site. Open your deployed app, open the browser's developer tools, and search the Sources and Network tabs for key-like strings. This is exactly what an attacker does.
You can also run your deployed app through the GreenGo checker. It is an automated scan, not a security audit.
How to rotate a leaked key
The OWASP Secrets Management Cheat Sheet lays out the response in order: revoke, rotate, delete, then log and investigate. In practice:
- Revoke or roll the key in the provider's dashboard. Stripe, Supabase, OpenAI and most other services let you create a new key and expire the old one. Stripe says to rotate an exposed key "immediately even if you aren't sure anyone saw it."
- Store the new key server-side. Put it in your platform's secrets store (Supabase Edge Function secrets, Vercel or Netlify environment variables without a public prefix, or a vault).
- Move the API call to the server. Instead of calling the provider from the browser, call your own backend function, which reads the key at runtime and forwards the request. Wiz recommends this proxy pattern for vibe-coded apps.
- Remove the old key from code. Delete it from the current files. Rewriting git history is optional and has side effects, and it does not help once a key has leaked, which is why rotation comes first.
- Check usage logs. Look at the provider's request logs and billing for activity you don't recognize.
- Add a guardrail. Set up a pre-commit hook or CI step with Gitleaks so the same mistake fails the build next time. Both OWASP and Stripe recommend scanning before code is committed.
Prompting your AI tool to avoid this
Most leaks start with a prompt like "connect to OpenAI." A more specific instruction helps:
- "Call the OpenAI API from a server-side function. Read the key from a secret, never from client code."
- "Do not use the
VITE_prefix for any API key." - "Use only the Stripe publishable key in the front end. Create the checkout session on the server."
Review the diff afterward and confirm the tool actually followed them.
Why buyers care
If you plan to sell your app, a key in the bundle is one of the first things a technical buyer will find. It also complicates handover: every key has to be rotated and moved into the buyer's accounts anyway.
On GreenGo, the Green Light badge includes a Gitleaks secrets check alongside DNS ownership, headers, dependency and delivery-file checks. It shows the check date, lasts 90 days, and is not a security audit or certification. When your app passes, list it on /sell. Listing is free.
Sources
- Env Variables and Modes — Vite
- Managing secret API keys — Stripe Docs
- Secrets Management Cheat Sheet — OWASP
- Common security risks in vibe-coded apps — Wiz Research
- The State of Secrets Sprawl 2026 — The Hacker News
- Gitleaks — LinuxLinks overview
Updated October 26, 2026.
