
๐ค Ghostwritten by GPT 5.4 ยท Fact-checked & edited by Claude Opus 4.6
On May 7, 2026, researchers reported that a large scan of publicly accessible vibe-coded apps found a recurring, preventable mistake: real secrets were embedded directly in front-end code and shipped to every visitor's browser. The core lesson is blunt: anything in the browser is public. If code runs on someone else's computer, that person can inspect it, copy it, and reuse anything sensitive it contains.
That does not mean every key visible in browser code is automatically a breach. Some keys are intentionally public โ a Supabase anon key or certain Firebase client configuration values, for instance โ when they are paired with proper access controls behind them. The danger starts when developers, or AI coding tools optimizing for "make it work," place secret keys, admin credentials, database passwords, or privileged tokens in the client bundle. At that point, the secret stops being a secret.
For vibe-coders, this is one of the most common and costly front-end security mistakes because the app often appears to work perfectly during testing. The problem is not whether it works. The problem is whether it exposes authority that should never leave a controlled server environment.
TL;DR: Front-end code runs in the user's browser and is fully inspectable; back-end code runs on infrastructure you control, which is where real secrets belong.
The simplest way to understand front-end vs. back-end keys is to stop thinking about files and start thinking about execution.
Front-end code includes React components, JavaScript bundles, CSS, and anything else downloaded into the browser. Once that code reaches the user's device, it is no longer private. A visitor can inspect it through:
Even if values are minified, obfuscated, or hidden behind a build step, they are still present if the browser needs them to run the app. Obfuscation is not protection. It only changes how annoying the secret is to find.
Back-end code runs on a server, serverless function, edge function, or other managed runtime that the user does not directly control. That is where real secrets belong:
The browser should call the back end, and the back end should use secrets on behalf of the browser when needed.
Researchers reported on May 7, 2026, that a scan of roughly 380,000 publicly accessible apps built with vibe-coding tools found about 5,000 with no authentication at all, and roughly 40% of those were exposing sensitive data or credentials, according to coverage by Axios and other secondary reports. Because the primary report was not independently reviewed here, those figures are best treated as reported approximations rather than exact audited counts. The lesson still stands even if the exact totals shift: client-bundle secrets are a widespread, real-world problem.
| Where a value lives | Can the user inspect it? | Safe for real secrets? | Examples |
|---|---|---|---|
| Browser / client bundle | Yes | No | React env vars exposed to client, embedded tokens, hardcoded credentials |
| Server runtime | No, not directly | Yes | Database credentials, service-role keys, private API secrets |
| Browser with publishable key | Yes | Sometimes, by design | Public SDK config, Supabase anon key, Firebase client config |
TL;DR: AI coding tools optimize for successful execution, not for trust boundaries, so they often choose the insecure shortcut that makes the demo work fastest.
A vibe-coding assistant sees the immediate task in front of it: connect this UI to that API, fetch data, save a record, make authentication work, get the app running. If the fastest path is to put a key directly into a React component or a client-side config file, many tools will do exactly that unless the prompt explicitly forbids it.
That happens because the model is usually optimizing for a local definition of success:
From the model's perspective, hardcoding a token into front-end code often "solves" the problem. The page loads. The request succeeds. The user sees the expected output. But "it works" is not the same as "it's safe."
In early development, a hardcoded key often looks harmless because:
That false sense of safety disappears the moment the app is deployed. Once the code is live, every visitor receives the same bundle. If a privileged secret is embedded there, every visitor effectively receives that secret too.
This is especially important in Firebase API key exposure conversations, because not every Firebase value visible in client code is automatically a secret. Firebase client configuration is often designed to be present in the browser. The real security question is what access controls sit behind it โ such as Firebase Security Rules โ and whether any truly privileged credentials were also exposed. The same nuance applies to Supabase: an anon key is meant to be used in client apps, but a service-role key is not.
TL;DR: Some keys are meant to be public, but any key that grants privileged access must stay server-side.
The phrase "publishable vs. secret keys" matters because it prevents an overcorrection. Not every key in front-end code is wrong. Some platforms intentionally use public identifiers or limited-scope keys in browser apps.
These values are often acceptable in client code when paired with proper controls:
These values are not safe because they are hidden. They are safe only when they are designed to be public and constrained by server-enforced rules, scopes, origins, or permissions.
These values must stay server-side:
A useful rule: if the key can create, delete, administer, bypass policy, access billing, or read data broadly, it is a secret.
The official Supabase documentation distinguishes between publishable (anon) keys for client use and service-role keys for trusted server environments. Firebase similarly documents client-side configuration separately from server-side administrative credentials. Those platform distinctions exist because the trust boundary is real, not optional.
| Key type | Intended location | Typical privilege level | Browser-safe? |
|---|---|---|---|
| Supabase anon / publishable key | Front end | Limited, policy-constrained | Yes, with RLS and proper policies |
| Supabase service-role key | Back end | Elevated / admin | No |
| Firebase client config | Front end | Public app identification with rules behind it | Yes, with Security Rules |
| Database password | Back end | Full database access | No |
| Billing / admin API token | Back end | Privileged account access | No |
The nuance matters because saying "all keys in the browser are bad" is inaccurate. The correct statement is sharper: any true secret in the browser is public the moment the app loads.
TL;DR: Audit the live app in the browser first, because the deployed bundle reveals what visitors can actually see.
The fastest security check is not theoretical. Open the deployed application and inspect what the browser received.
Do this now:
keytokensecretpasswordservice_roleapiKeyThis catches many obvious leaks because production bundles often contain exactly what the browser executed. It also helps identify values injected through build-time environment variables that were assumed to be private but were actually compiled into client code.
If a real secret appears in the browser:
Use this prompt with an AI coding agent to review a project:
Audit this codebase for secrets exposed to the browser. Identify every environment variable, hardcoded key, token, password, credential, or SDK config that is included in front-end code, bundled client JavaScript, browser network requests, or public build output. Classify each item into one of three buckets: (1) safe to expose in the browser because it is explicitly publishable/public and designed for client use, (2) risky and needs human review, or (3) must be moved server-side immediately because it is a secret or grants privileged access. For each finding, show the file and code path, explain why it is or is not safe, and propose the exact remediation: move to server-side environment variable, proxy through a server endpoint, replace with publishable key, or rotate and revoke. Assume anything in the browser is public.
That prompt is useful because it asks the model to reason about exposure by runtime location, not just variable names.
TL;DR: The secure pattern is simple: the browser uses public identifiers or publishable keys; the server handles privileged actions with real secrets.
A secure app does not require every feature to move fully to the back end. It requires the right boundary.
Use this pattern:
This is the difference between a public app identifier and an actual secret. One identifies the app. The other authorizes power.
Vibe-coding compresses the distance between idea and deployment. That speed is useful, but it also removes the pause where traditional teams might ask, "Should this value ever leave the server?" When AI generates both the UI and the integration layer, it can blur the line between convenience and security.
The fix is not to stop using AI tools. The fix is to enforce one non-negotiable rule during generation and review: never place secret credentials in code that ships to the browser.
A good mental model:
No. Some values are intentionally public, including certain publishable SDK keys and app configuration values. The real issue is whether the exposed value grants privileged access such as admin actions, broad data reads, writes, billing control, or policy bypass.
A Supabase anon key is designed for client-side use, but only in combination with properly configured Row Level Security and restrictive policies. The key itself is not the protection layer; the database rules behind it are.
Because the insecure version often produces a working app with fewer steps. The model sees a direct path to a successful API call, but it does not inherently understand that code shipped to the browser is visible to every visitor unless the prompt, tooling, or review process enforces that boundary.
Ask what power the value grants. If it can administer a service, access billing, bypass access controls, read broad datasets, or write data without user-scoped restrictions, it belongs on the server.
Open the deployed app, press F12, and search the Sources and Network tabs for terms like key, token, secret, and password. Then review whether each exposed value is intentionally publishable or actually a privileged secret that must be rotated and moved server-side.
The browser-versus-server boundary is one of the few security rules that does not get more complicated with scale: if code runs in the browser, assume the user can see everything it contains. As vibe-coding tools continue to accelerate how quickly apps move from prompt to production, the teams that stay safe will be the ones that treat "works in the browser" as the start of a security review, not the end of it.
Discover more content: