Skip to content

Security basics

An API key exposed in your frontend: what 30,998 apps shipped

An API key exposed in your frontend is usually a Google Maps key. We scanned 30,998 live vibe-coded apps and counted which secrets actually leak.

Vlad Tkachenko8 min read
A page of app code with eight key-shaped values highlighted in it, most of them ordinary and one drawn in red.

In short

  • An API key exposed in your frontend is nearly always the harmless kind. We found a key worth naming in 1,332 of 30,998 apps, and 1,080 of those carried a Google API key and nothing else.
  • The Supabase service_role key, the one every tutorial warns about, appeared in 3 apps out of 30,998. A Stripe secret key also appeared in 3.
  • Keys that spend money or read data by their own shape appeared in 54 apps. An open Supabase table appeared in 2,096.

Someone opens your app, presses F12, and tells you there is an API key exposed in your frontend. The word they use is usually "leaked". They rarely say which key, and the advice you find afterwards treats every one of them as the same emergency.

They are not the same emergency, and we can now put numbers on the gap. Between 12 and 14 August 2026 we ran nine external checks over 30,998 live apps built with Lovable, Bolt, v0, Replit and Base44, and read the JavaScript each one hands to a browser. This is what was in it.

Is an API key exposed in your frontend actually a problem?

Usually not, and the shape of "usually" is more lopsided than we expected.

We found a key worth naming in 1,332 of the 30,998 apps. In 1,080 of those, the only thing we found was a Google API key, which is the credential that is supposed to be in your page. What protects that one is a setting held on Google's side, and hiding it was never part of the arrangement.

The other 29,666 apps shipped nothing our secrets check treats as a problem. That figure needs one qualification to be honest. Publishable keys are excluded from it: a Supabase anon key or a Stripe pk_ key belongs in the browser, so we mark it as something you got right and it never enters these counts.

What we counted, and what we could not

We loaded each app the way a visitor does, in a real browser, and read the JavaScript it downloaded. Everything below is a shape found in that code.

We match key formats we know. Stripe, OpenAI, Anthropic, AWS, Google and Supabase have recognisable prefixes, and a Supabase JWT states its own role in readable text in the middle section. A credential in a format we do not recognise is not in these numbers, so read them as a floor.

We never used a key we found. Not once, on any app. We recorded the type and a masked hint of the form sk_live_…a1b2, and the real value was never written down anywhere.

No app is named. Not here, not in the dataset, not anywhere we publish.

Nearly all of these are apps published on a builder's own domain. The full method, the sample and every figure behind this article are in our scan report, including the parts we could not measure.

What the 30,998 apps were actually shipping

One table, ordered by how often we saw each thing. The secrets check completed on every one of the 30,998 apps, so every count below is out of all of them.

What we found in the browser codeApps
Google API key1,142
A high-entropy value beside a "secret" or "password" name204
OpenAI key33
AWS access key9
Anthropic key5
Supabase service_role key3
Stripe secret key3
Stripe restricted key2

The rows add up to more than 1,332 because one app can carry two of these. Sort the same 1,332 apps into buckets that do not overlap and the picture gets starker: 1,080 had a Google key and nothing else, 198 had a high-entropy value that may or may not be a real credential, and 54 had a key that is a genuine secret by its own shape.

Every app where we found a key at all, sorted into three groups that do not overlap. The red section is the one the warnings are about.

That second bucket is worth naming carefully. A high-entropy value sitting next to a word like secret or password might be a live credential, or it might be a session identifier, a build hash or a public token with an unlucky name. We flag it as worth a look, and from outside nobody can tell you which it is.

The 54 in the third bucket are the real thing. All but two of them came out graded D or F, because a single critical finding caps the grade at D no matter what else the app got right. The largest single group inside it is the OpenAI key, at 33, and that one has no setting that would make it safe in a browser.

The key everyone warns about was the rarest thing we found

Three apps. That is how many shipped a Supabase service_role key, the one that walks past every table rule you have written. A Stripe secret key also appeared three times.

Set that against the other half of the same sweep. Of the apps we scanned, 8,429 named a Supabase project, and both findings sit inside that one group. Three of them shipped the service_role key. In 2,096 of them, at least one table answered a request carrying no login at all, and in 394 that table was named after people: users, profiles, customers, orders.

Both counts come from the same 8,429 Supabase apps. The upper row is the finding owners are warned about.

The 2,096 is a floor. 4,749 of the 8,429 never answered our database check, for reasons we cannot see from outside, and those are recorded as unknown rather than as clean. The three service_role keys are an exact count, because a key is read out of code that every app hands over.

Why the warnings point at the wrong key

We only see the outside of these apps, so we cannot tell you why. What we can show is which mistake survives.

Copying the wrong Supabase key is genuinely easy to do. In an older project anon and service_role sit next to each other in the same dashboard panel, they are the same length and the same shape, and nothing breaks if you take the wrong one. It still only happened three times in 30,998 apps, because nothing pushes you towards it. Pasting either key makes the app work.

The open table has a force behind it. Row Level Security gets switched on, the app stops showing data, a policy that allows everybody gets written to make it work again, and from that moment on the dashboard reports the table as protected. Switching it on is not the same as being protected. Our scan grades that table a critical finding on a day your dashboard is showing you the switch turned on and a policy in place.

How to find an exposed API key in your app, for free

Start by hand, because it costs five minutes and needs nothing installed. Open your live site, view the page source, and search it for four strings: AIza for a Google key, sk_ for a Stripe or model-provider secret, service_role for the Supabase key that ignores your rules, and eyJ for any Supabase token. Anything that comes back is already in the hands of every visitor you have.

That gets you the page itself. What it misses is the JavaScript the page loads afterwards, which is why our own scanner opens an app in a real browser and reads the bundles instead of the HTML. Searching by hand also cannot tell you whether the eyJ token you found is the anon key or the secret one, because the two are the same length and the same shape.

Our free scan covers both. It loads your app in a real browser, reads the code that actually arrives, decodes each Supabase token and reports the role written inside it, so a publishable key comes back marked correct instead of buried in a wall of red. You get a grade, a score and the counts on screen in about 20 seconds with no account. Give an email address and you get the detailed list too, with a fix written for your builder that you can paste straight in.

Three things it will not do, which are the reasons it is safe to run on a live app: it never logs in, it never writes anything, and it never keeps a key it finds. An exposed secret is stored as a masked hint like sk_live_…a1b2, and the real value is discarded. Scan your app, or read what each of the nine checks looks at first.

What to do, in the order the numbers suggest

What to do

  • Identify the key before you react to it. The prefix answers it for Stripe and for newer Supabase keys; for an older Supabase key, the role field inside the token decides.
  • If it is a Google key, restrict it rather than hiding it. Websites restriction, your domain, only the APIs you use, plus a daily quota cap. Free, about five minutes, no code change.
  • If it is a genuine secret key, rotate it first. Deleting it from your code closes nothing, because the old value is still in your version history and in cached copies of your site.
  • Then go and read your table rules, which is where the numbers say the real exposure is. Start with the tables holding people.
  • Check billing and logs after any secret-key exposure. Rotation stops what happens next, and says nothing about what already happened.

Work through the whole list in one sitting with the 10-minute security checklist, which covers the key and the table rules alongside the other things worth closing in a newly launched app. And if the database half of this article is the part that worries you, it has a census of its own: can anyone read your Supabase database.

FAQ

How do I know if my API key is leaked?

Open your live site, view the page source, and search it for the shapes: AIza for a Google key, sk_ for a Stripe or model-provider secret, and eyJ for a Supabase JWT. Anything that turns up is in the hands of every visitor already. Our free scan does the same read from outside and reports what it can see in about 20 seconds, with no account needed for the grade.

Is a hardcoded API key always a security problem?

No, and treating it that way is how people learn to ignore the warning. Some keys are published on purpose: a Supabase anon key, a Stripe pk_ key and a Firebase web config key are all designed to sit in the browser, and what protects your data is the rules behind them. A secret key is the opposite case and belongs on a server. The prefix tells you which one you are looking at.

Someone said my Supabase key is exposed. Is that the bad one?

Almost certainly not. Both Supabase keys look alike, so read the role inside the token or check the prefix: anon and sb_publishable_ are meant to be public, service_role and sb_secret_ are not. We found a service_role key in 3 apps out of 30,998, so the odds are heavily on the harmless one. If it is the secret one, rotate it in the Supabase dashboard today.

What should I actually worry about instead?

The table rules on your database. Of the apps where we could complete that check, more than half had at least one table that answered a request carrying no login at all, and in 394 of them the open table was named after people: users, profiles, customers, orders. That is far commoner than any leaked key and it is much quieter, because the app works exactly the same either way.

I removed the key from my code. Is it closed now?

Not on its own. The old value still exists in your version history and in any cached copy of the page, so anyone who already collected it can keep using it. Rotating the key in the provider dashboard is what actually closes the door, and it is the first step rather than the last. After that, check billing and logs for usage you cannot account for.

Written by

Vlad Tkachenko

Founder, Reeve

I spend my time looking at apps built with Lovable, Bolt, v0, Cursor and Replit, and at the short list of mistakes that keep turning up in them.

More about the author

Read next

All articles

Not sure where your own app stands?

Run a free scan and get a plain-language grade from A to F in about 20 seconds. No account, no card.

Scan your app free

Automated external check, not a full audit. Absence of findings is not a guarantee of safety.