Vibe coding exposed API keys: how they leak, how to check

Vibe-coded apps leak API keys via client-bundle hardcodes, git history, and the Supabase service_role mix-up. How to check, fix, and what a scan misses.

Yes, vibe-coded apps leak API keys, and it is common. The usual cause is that AI-generated code hardcodes a secret directly into the client bundle the browser downloads, or commits it to a public repo. The two canonical shapes are the Supabase anon-vs-service_role mix-up (one key is safe in the frontend, the other is full admin access that should never be there) and the “weekend app, five-figure bill” — an exposed billing-backed key like OpenAI or AWS that bots find and drain before you check the invoice.

This guide is the one page in the search results that is honest about the part everyone glosses over: a scan run from outside your app can verify the conditions that decide how bad a leak gets, but it structurally cannot see the leaked key itself. We know because we run that kind of scan — and our own audit data has a hole exactly where secrets would be.

At a glance

  1. Where keys leak — hardcoded in the client bundle; committed to a public repo or left in git history; shipped in .env.example with real values; or the Supabase service_role key (full admin) used where the anon key (RLS-scoped, safe) belonged.
  2. How to check — grep your project for sk_, key=, secret, password, token; a literal string is a problem, a process.env.X reference is fine; check git history; run a dedicated secret scanner.
  3. What to do if exposed — rotate the key first (assume compromise the moment it is public), then remove it from code, move it to .env, .gitignore that file, scrub git history, and watch billing.
  4. What an external scan can vs can’t catch — a functional/black-box audit catches the header and transport gaps (no CSP, frameable, no HSTS, mixed content, cookies without Secure) that set the blast radius; it cannot see the key in the bundle or git. You need both a secret scanner and a functional audit.

Can a vibe coded app leak API keys?

Yes — and the way these apps are built makes it more likely, not less. When you describe a feature to an AI and it writes the code, you are several steps removed from where the secret ends up. The model wires up a Supabase client, an OpenAI call, a Stripe integration — and it has to put a key somewhere. If it puts a literal string in a file that gets bundled and shipped to the browser, or in a file that gets committed, the secret is now public, and you may never have read that line of code. That gap — between describing a feature and shipping whatever the model wrote — is exactly the assumption behind QA built for vibe coders: the checks have to catch what you didn’t read.

Two vendor scans put numbers on the scale. Escape.tech scanned over 5,600 publicly available vibe-coded applications (built on platforms such as Lovable.dev, Base44, and Create.xyz) and, in a report published Oct 29, 2025, found more than 2,000 vulnerabilities, 400+ exposed secrets, and 175 instances of PII including medical records and IBANs. Separately, Wiz Research reported on Sept 18, 2025 that 1 in 5 organizations building on vibe-coding platforms are inadvertently exposing themselves to risk — passwords embedded in client-side JavaScript, third-party API keys, and database credentials sitting in the bundle.

To be clear: those are the vendors’ scans of other people’s apps, not ours. They establish that the problem is real and widespread. What we can add — from measuring real launches ourselves — comes later in this piece, and it includes an honest accounting of what our kind of measurement cannot see.

Where the secrets actually leak (the four shapes)

Almost every leak in a vibe-coded app is one of four shapes. Knowing which one you have determines what you do about it.

1. Hardcoded in the client bundle. The most common and the most dangerous. Anything in your frontend code ships to the browser, and anyone can open dev tools and read it. A key written as const key = "sk-..." in a React component is not hidden — it is published. This is the shape Wiz called out: sk-... OpenAI keys and database credentials living in the JavaScript a browser downloads.

2. Committed to a public repo or left in git history. Even if a key is correctly read from an environment variable in your current code, the secret can still be in your git history — from an earlier commit, before you “moved it to .env.” Git does not forget. Removing a key from the current file does nothing to the commit that introduced it; the value is still there for anyone who clones the repo.

3. .env.example shipped with real values. .env.example is supposed to be a template with placeholder values, committed to the repo so collaborators know which variables to set. When an AI tool — or a hurried copy-paste — fills it with the real values and commits it, you have published your secrets under a filename that everyone trusts to be safe.

4. The Supabase anon-vs-service_role confusion. This is the single most misunderstood case, so get it exactly right. Supabase issues two keys. The anon key is designed to be embedded in frontend code — it is scoped by your Row Level Security (RLS) policies, and exposing it is expected and safe when RLS is configured. The service_role key bypasses all RLS and grants full admin access to your database; it must never reach the browser. Per Supabase’s own docs, the anon key belongs in the client and the service role key belongs only on a trusted server. AI code generators compound the danger in a specific way: when an RLS-blocked query fails, they often suggest swapping in the service_role key to make the error go away — instead of fixing the policy. That “fix” turns a safe frontend into a full-admin database key sitting in the bundle. (The dedicated Vibe App Scanner write-up documents exactly this pattern.)

How to check for exposed secrets in a vibe coded app

You can do a first pass by hand in a few minutes. The goal is to find literal secret values that should be references instead.

  1. Grep the project for the obvious markers. Search the whole tree for sk_, sk-, key=, secret, password, token, AKIA (AWS access keys), and service_role. Most editors will do this across all files at once.
  2. Distinguish a literal from a reference. A literal string value — "sk-abc123..." or "eyJhbGci..." — is the problem. A reference like process.env.OPENAI_API_KEY or import.meta.env.VITE_... is fine; that reads the value at runtime from the environment, not from your shipped code. (Caveat: on the frontend, anything prefixed for client exposure — VITE_, NEXT_PUBLIC_ — still ends up in the browser even via process.env. A service_role key behind NEXT_PUBLIC_ is still exposed.)
  3. Check git history, not just the current files. Run a history search (git log -p -S 'sk-' or a tool like git-secrets/gitleaks) — removing a key from the current code does not remove it from past commits, and that is where leaked keys most often hide.
  4. Run a dedicated secret scanner. Tools like Gitleaks or TruffleHog read your source and git history and flag high-entropy strings and known key formats. This is the right tool for the bundle/source/history layer — and, as the next section explains, it is not the same tool as a functional audit. The dev.to guide “Your vibe-coded app might be wide open” walks through the same manual procedure if you want a second reference.

What to do if my API key was exposed on GitHub

The ordering here matters more than the steps themselves. Do them in this order:

  1. Rotate the key first. The moment a secret is public, assume it is compromised. Automated bots scrape new public commits within minutes specifically to harvest keys, so revoking the old key and issuing a new one comes before you clean up the code. Cleaning up first leaves a live, exposed key in the wild while you work.
  2. Remove the literal value from your code and replace it with an environment-variable reference.
  3. Move the secret to a .env file and add .env to .gitignore so it is never committed again.
  4. Scrub it from git history. Editing the file is not enough — use git filter-repo (or the platform’s secret-removal flow) to purge the value from past commits. If the repo is public and the key was billing-backed, treat history scrubbing as cleanup after rotation, never instead of it.
  5. Watch the billing console. Exposed billing-backed keys are drained fast. Exposed OpenAI keys have burned through thousands of dollars in API charges overnight, and exposed AWS keys have triggered five-figure cloud bills within hours — a weekend app becomes a five-figure problem before the owner notices, because small teams check invoices after the damage is done. Set a usage alert if your provider supports one.

Can an external scan find exposed API keys? (the boundary)

This is the question the rest of the search results dodge, and it is the reason this page exists. The honest answer is: partly, and not the part you would hope.

A functional, black-box audit — the kind Prufa runs for free, and the kind that drives most automated “scan your site” tools — looks at your app from the outside, the way a browser does. It can verify the header and transport layer that decides a leak’s blast radius: whether you send a Content-Security-Policy (without one, one injected script owns the page — and every third-party tag you load is trusted completely), whether the page can be framed by any site (clickjacking), whether HSTS is set, whether there is mixed content, whether cookies carry the Secure attribute. These are the “the secret leaked and nothing contains the damage” conditions, and they are exactly what an external audit is good at.

What that audit cannot see is the leaked key itself. A service_role key or an sk-... string hardcoded in the client bundle, secrets in .env.example, keys buried in git history — a black-box functional audit never reads your source code, so it is structurally blind to all of them. This is not a limitation we can engineer away with a better scan; it is a property of looking from the outside.

So the two tools are complementary, not substitutes. A secret scanner reads your source and git history and finds the key. A functional audit checks the headers and transport that determine how much an exposed key can hurt you. Running one and assuming you are covered is exactly the false sense of safety this guide is meant to prevent. This boundary is the same principle our product is built on — the LLM navigates, plain code verifies, and we never claim a capability we do not have.

What we found measuring real launches

We audit real launches, and the data backs up the boundary above in an uncomfortable way.

In our June 2026 audit of 14 r/SideProject launches, 11 of 14 served pages with no Content-Security-Policy header and could be framed by any site — the most consistent header-hygiene gap we measured. That is the blast-radius layer, and a black-box audit sees it clearly.

In our June 2026 audit of 49 Show HN launches, 38 of 49 sites had at least one critical verified finding (40 criticals total across the set). Secret-related findings: zero — because our audit does not scan for secrets. That is not a clean bill of health; it is a hole in our own data exactly where secrets would be. The highest-stakes failure mode in this whole topic is the one our kind of measurement structurally cannot see, which is precisely why we tell you to pair a functional audit with a secret scanner rather than trusting either alone.

One honest caveat on those numbers: the n=49 and n=14 samples are launches generally, not vibe-coded apps specifically. They measure the header/transport hygiene of real shipped products, which is the layer this article is about — not a survey of secret leaks. For that, the Escape and Wiz figures above are the better source.

Precision on the Lovable incident

Most coverage blurs “the Lovable leak” into one undifferentiated scare. It is worth getting right, because the actual event is a different failure mode than a client-bundle key leak. The April 2026 Lovable incident was a BOLA (Broken Object Level Authorization) flaw: per Halborn’s analysis, a free authenticated account could read other users’ source code, chat history, and database credentials. Lovable shipped a fix within roughly two hours of the April 20 public report, though older projects created before November 2025 still returned 200 OK to unauthorized users for a time.

That is an authorization leak — the app failed to check that the requester was allowed to see the object — not a secret hardcoded in a client bundle. Both end in “credentials got out,” but they are different bugs with different fixes, and a secret scanner would not have caught the BOLA flaw any more than a header audit would have caught a hardcoded key. The distinction matters because it is the same lesson as the boundary section: different failure modes need different tools, and no single scan covers all of them.

The honest recommendation: two tools, not one

If you shipped a vibe-coded app and you are worried about leaked keys, do two things. First, run a secret scanner over your source and git history — that is the only tool that can find the key itself, and it is the one that addresses the headline risk. Second, run a functional audit (Prufa’s free audit for vibe-coded apps is one option) to close the header and transport gaps — no CSP, frameable pages, missing HSTS, mixed content — that determine how much damage an exposed key can do. If you build in the same editor the bundle came out of, you can wire that audit into Cursor and run it over MCP without leaving the tab. Our vibe-coding testing checklist and step-by-step guide to testing a vibe-coded app cover where this fits in the broader pre-launch pass.

Neither tool alone is enough, and any page that tells you “scan with us and you’re safe” is selling you the comfortable half of the answer. The uncomfortable half — that the leaked key lives in a layer an external scan can’t reach — is the part worth knowing before you ship.

Frequently asked questions

Can a vibe coded app leak API keys?

Yes. AI-generated code routinely hardcodes a secret into the client bundle or commits it to a public repo. Escape.tech scanned over 5,600 publicly available vibe-coded apps on Oct 29, 2025 and found 400+ exposed secrets; Wiz reported on Sept 18, 2025 that 1 in 5 organizations on vibe-coding platforms inadvertently expose risk. The four common shapes: client-bundle hardcode, git history, .env.example with real values, and the Supabase service_role key.

Is the Supabase anon key safe to expose?

Yes. The Supabase anon key is designed to sit in frontend code and is scoped by your Row Level Security (RLS) policies, so exposing it is expected and safe when RLS is configured. The service_role key is the opposite: it bypasses all RLS and grants full admin access, so it must never reach the browser. AI tools compound the risk by suggesting service_role when an RLS-blocked query fails instead of fixing the policy.

What do I do if my API key was exposed on GitHub?

Rotate the key first. The moment a secret is public, assume it is compromised — bots scrape new commits within minutes, so revoking and reissuing comes before anything else. Then remove the literal value from your code, move it to a .env file, add .env to .gitignore, and scrub it from git history (the commit keeps the key even after you edit the file). Finally, watch your provider's billing console for unexpected usage.

Can an external scan find exposed API keys?

Only partly. A black-box functional audit verifies the header and transport layer that decides a leak's blast radius — missing CSP, a frameable page, missing HSTS, mixed content, cookies without Secure. It structurally cannot see a key hardcoded in the client bundle, in .env.example, or in git history, because it never reads your source. Those need a dedicated secret scanner. The two are complementary, not substitutes — use both.