FlawPilot
From the blog

What Your Frontend Is Telling Strangers

Quick answer: anything your app sends to the browser can be read by anyone who visits it, so a secret key that ends up in your frontend code is not a secret anymore. Some keys are designed to be…

Virendra PatilSecurity research26 Oct 202611 min read

Quick answer: anything your app sends to the browser can be read by anyone who visits it, so a secret key that ends up in your frontend code is not a secret anymore. Some keys are designed to be public, like a Stripe publishable key or a Supabase anon key, and are fine to ship as long as the backend behind them is locked down. Others, like a Stripe secret key, an OpenAI key, or a database admin key, must only ever live on a server. The second blind spot is related: hiding a button or a page in the interface does not stop anyone from doing what that button does. Real protection has to happen on the server, where the visitor can't change the rules.

Vibe-coded apps get here honestly. You ask the AI to "connect to OpenAI" or "add payments," it wires the key straight into the code that runs in the browser because that's the fastest way to make the demo work, and everything appears to work. Nothing on screen tells you that the key is now sitting in a file any visitor can download.

The same goes for admin features. If the AI built an admin panel and only shows the link to admin users, the app looks secure. But looking secure and being secure are two different checks, and only one of them shows up when you click around your own app.

In short: the browser is a public place. Treat everything in your frontend as something a stranger can read and change, and make sure your secrets and your permission checks live on the server.

This is part 6 of our series on what vibe-coded apps miss. Start with the overview here.

What it looks like

Most of the time, it doesn't look like anything. That's the problem. An exposed key causes no error and no visible bug. The symptoms usually show up later and somewhere else:

  • A surprise bill. Someone finds your AI provider key and uses it for their own projects, and the charges land on your account.
  • Data you didn't share. Records from your database show up somewhere they shouldn't, because a key with full access was readable in the browser.
  • Actions you didn't allow. A regular user changes their own plan to "pro," deletes another user's content, or opens an admin page by typing its address directly, because the only thing stopping them was a hidden button.

None of these need a skilled attacker. Reading what a website sends to the browser is a normal feature of every browser, and automated tools scan public sites for key-shaped strings all the time.

Public keys vs. secret keys

Not every key in your frontend is a mistake. Many services deliberately give you two kinds of keys, and knowing which is which is most of the battle.

Key typeExampleSafe in the browser?What protects you
Publishable / public keyStripe publishable key (starts with pk_)YesIt can only start actions, not complete sensitive ones. The secret key on your server does that.
Anon / client keySupabase anon keyYes, if your database rules are onRow Level Security policies decide what each user can read and write. Without them, the anon key can reach far more than you intended.
Secret / server keyStripe secret key (starts with sk_)NoNothing. If it's public, anyone can act as your business.
AI provider keyOpenAI or Anthropic API keyNoNothing. Anyone holding it can run requests billed to you.
Admin / service keySupabase service role key, database passwordsNoNothing. These bypass the rules that protect your data.

A simple test: if the service's documentation calls it "secret," "service," "admin," or "server," it doesn't belong in the browser. If it's called "publishable," "public," or "anon," it's designed for the browser, but it still depends on the backend being configured correctly.

The Supabase case trips up many vibe-coded apps. The anon key is meant to be public, so seeing it in your code isn't a problem on its own. The problem is when the database tables behind it have no Row Level Security policies. Then the "public" key can read or change everything, and the fact that it was meant to be public stops being reassuring.

Hiding a button is not access control

This is the second half of frontend security, and it catches people who have no keys exposed at all.

When your app hides the "Delete user" button from regular users, it is making a display decision, not a security decision. The browser is running code that you sent to the visitor, and the visitor controls their own browser. They can open the page address directly, call the same backend request the button would have made, or edit the page in their browser to show what you hid.

Real access control means the server checks every request: who is asking, and are they allowed to do this specific thing to this specific record? If the server says yes to anyone who asks, it doesn't matter how carefully the interface hides things.

Some common patterns in AI-built apps:

  • Role checks only in the UI. The app reads isAdmin from the user's profile and shows or hides the admin menu, but the admin endpoints accept requests from any logged-in user.
  • Trusting what the browser sends. The browser tells the server "this is user 42's order" and the server believes it, instead of checking that the logged-in user actually owns order 42.
  • Price or plan set in the frontend. The checkout amount or the subscription tier is decided in browser code, so a user can change it before it's sent.

Why AI tools miss it

AI builders optimize for "it works when I try it." When you test your own app, you are a well-behaved user clicking the buttons in order, so frontend-only protection passes every test you run.

There's also a practical shortcut. Putting a key directly in browser code is the fewest steps to a working demo. Setting up a small server function to hold the key and make the call on your behalf takes more steps, and the AI often skips it unless you ask. Some tools do warn about this, and some separate public and secret keys automatically, but it depends on the tool, the prompt, and how the code changed over many iterations. A key that was safely on the server in version one can drift into the browser by version twenty.

Check it yourself

You can do a basic version of this check on your own site in about ten minutes, with no code. Only run these checks on apps you own or are responsible for.

  • Open your live site in Chrome and view the source. Right-click the page and choose "View page source." Press Ctrl+F (Cmd+F on Mac) and search for sk, secret, servicerole, api_key, and apikey.
  • Check the loaded scripts. Open DevTools (F12), go to the Sources tab, and use "Search all files" (Ctrl+Shift+F or Cmd+Option+F). Run the same searches. Most of your app's code lives in these JavaScript files, not in the page source.
  • Look at network requests. In DevTools, open the Network tab, use your app normally, and click on requests going to third-party services. If you see a secret-style key in a request header, that request is happening from the browser and the key is exposed.
  • Try the hidden pages. Log in as a regular, non-admin user and type the address of an admin page directly into the address bar. If it loads, or loads briefly before redirecting, ask a developer to confirm the server is checking permissions too.
  • Check your database rules. If you use Supabase, open the dashboard and confirm Row Level Security is enabled on every table that holds user data, with policies that match who should see what.

If you find a secret key, treat it as already compromised. Removing it from the code isn't enough, because copies may already exist. Rotate it in the provider's dashboard first, then move it to the server.

How FlawPilot spots it

FlawPilot checks this from two directions: what your live site exposes to the public, and what's sitting in your code.

The quick scan's Security pillar looks at your live site the way a visitor would. It checks for public secret exposure, and it also reviews your security headers, TLS setup, DNS and email authentication, and known CVEs in what your site serves. That gives you a picture of what a stranger can see without logging in. You can run a quick scan on your site here.

The code scan goes deeper when you connect your repository. It runs checks for exposed secrets across the codebase, along with code security, code quality, and vulnerable dependencies. This catches keys that were committed to the repo even if they never reached the live site, which matters because repositories get shared, forked, and made public more often than people expect.

Reading the results:

  • An exposed secrets finding flags a likely secret so you can find and review it. Check it against the public vs. secret table above. A publishable key is expected. A secret, service, or AI provider key needs rotating.
  • Code security findings flag risky patterns in the code itself. They won't prove every page checks permissions, but they show where a developer should look first.
  • AI fix suggestions are attached to code scan findings, so you can see what a fix would look like and hand it to whoever maintains the app.

FlawPilot shows you where the exposure is. It doesn't replace a developer confirming that every server endpoint checks permissions, but it tells you exactly where to start that conversation.

What "fixed" looks like

A properly fixed app has a few clear traits you can verify or ask about:

  • Secret keys live only on the server, in environment variables on your hosting platform, and the browser calls your own server function, which then calls the outside service.
  • Any secret that was ever exposed has been rotated, so the old value no longer works.
  • Every sensitive request is checked on the server, including who the user is and whether they own the record they're touching.
  • Database rules are enabled if you use a client key like Supabase's anon key.
  • Prices, plans, and roles are decided on the server, never trusted from the browser.

Urgent vs. can wait

SituationPriority
Secret, service, or AI provider key visible in the browserUrgent. Rotate today, then move it to the server.
Admin actions that work for regular usersUrgent. Anyone who finds it can use it.
Supabase anon key with no Row Level Security on user tablesUrgent. Your data is effectively public.
Secret key in the repo but not on the live siteHigh. Rotate it, and keep the repo private.
Publishable key visible in the browserExpected. No action needed beyond confirming its type.
Missing security headersWorth fixing soon, but lower than the items above.

Frequently asked questions

Not if it's the anon key and your tables have Row Level Security policies turned on. The anon key is designed to be public. The service role key is not, and should never appear in browser code.

Final Thoughts

The frontend is the part of your app you hand to every visitor. That makes it the right place for your design and your public keys, and the wrong place for anything you'd mind a stranger reading or changing.

The good news is that this is one of the most fixable problems in a vibe-coded app. A key can be rotated in minutes, and permission checks are a well-understood pattern. The hard part is knowing it's there, because nothing on screen tells you.

Run the self-check, or let a scan do it, before your app gets real users and real data. Finding an exposed key yourself is a small chore. Finding out from a bill or a customer is a much bigger one.

How FlawPilot helps

FlawPilot finds security and quality issues in your AI-built app and shows you how to fix them. It checks your deployed site across security, performance, infrastructure, and SEO, and scans your source code for vulnerabilities, hardcoded secrets, and vulnerable dependencies.

Every finding is prioritized and explained in plain English, with the actual fix: the configuration change, DNS record, security header, or code change needed. For supported findings, AI-powered guidance adds step-by-step instructions and suggested code fixes.

Connect your Git provider to scan your repository alongside your live site, so application findings, code vulnerabilities, secrets, and dependency issues all land in one place.

It fits your existing workflow too: a REST API for scores and findings, an embeddable security badge, and an MCP server so tools like Claude, Cursor, or ChatGPT can read your findings and help you work through them.

The boundaries are clear: the public website scan reads only publicly accessible signals, with no agent or credentials required, and source-code scanning is opt-in and read-only. Fixes are never applied or merged without your review.

Vibe Coded App ReviewAPI Key ExposedFrontend SecurityExposed SecretsAI App BuilderAccess Control

Verify your AI-generated app is production-ready.

117 security checks in 60 seconds - free, no account needed.

Scan one page

Enter a URL - no account, no install.

Run a Site Health check

Requires a free account

Crawls every page we can reach and scores each one, so a slow template deep in the site stops hiding behind a healthy homepage.

Scan your source code

Requires a free account

Connect a Git provider to check for vulnerabilities, secrets, and risky dependencies.

Featured on