← Blog

5 production-readiness bugs vibe-coded apps ship with

Your AI built something impressive. The build passes, the tests are green, it’s live on a real URL, and it looks finished. So you’re about to put real users — and real data — behind it.

Here’s the uncomfortable part: “it runs” and “it’s safe to launch” are two completely different things. The tools that generate these apps optimise for working, not for holding up under real users and bad actors. Over and over, the same handful of issues show up — and almost all of them are invisible from the outside, because every checkmark on the dashboard is still green.

These are the five we find most. None of them require exotic attacks. Each is something a real person reading your code spots in minutes — and a scanner usually misses, because it doesn’t understand what your app is trying to do.

1. Secrets shipped to the browser

The classic. A STRIPE_SECRET_KEY, a database service-role key, or an admin API token ends up bundled into the client-side JavaScript — usually because it was read without a server-only prefix, or used in a component that runs in the browser.

Anyone can open DevTools, look at the network tab or the bundle, and read it. From there they can charge cards, read your whole database, or impersonate your backend.

How to check it yourself: search your repo for your secret names and confirm they’re only ever read on the server. In Next.js, anything that should stay private must not start with NEXT_PUBLIC_. Then open your deployed site, view the source/bundle, and grep it for sk_, service_role, and your key prefixes. If you find them, they’re already public — rotate them immediately.

2. Routes that look protected but aren’t

The app has a login. There’s a nice auth screen. So it feels secure. But the /admin page, the “internal” dashboard, or the API route behind it often has no actual server-side check — the UI just hides the link.

Hiding a button is not access control. If someone types the URL or calls the endpoint directly, they’re in.

How to check it yourself: log out (or open an incognito window) and paste your most sensitive URLs and API endpoints directly. If you get data instead of a redirect or a 401, the guard is missing. Every protected route needs the check enforced on the server, on every request — not in a useEffect that runs after the page already loaded.

3. The database is wide open

This one is quietly catastrophic, and it’s especially common with Supabase and Firebase. Row-Level Security (RLS) is off, or the rules are set to true “just to get it working” — and never tightened. The result: any user (or anyone with your public anon key, which is meant to be public) can read and often write every row in every table.

Your green checkmarks won’t catch this. The app works perfectly for you, because you’re only ever looking at your own data.

How to check it yourself: in Supabase, confirm RLS is enabled on every table and that each policy actually scopes rows to the current user. With Firebase, read your security rules line by line and assume an attacker has your config — because they do.

4. One tenant can read another tenant’s data

If your app is multi-tenant — teams, workspaces, organisations — the dangerous bug is a query that filters by org_id on the client or trusts an ID sent by the browser. Swap the ID in the request and you’re reading someone else’s customers, invoices, or messages.

This is the bug that turns into a headline. It’s also the one a generic scanner almost never finds, because the code looks completely normal — the flaw is in trusting input you shouldn’t.

How to check it yourself: for every query that returns tenant-scoped data, confirm the tenant boundary is enforced server-side from the authenticated session — never from a parameter the client can change.

5. No rate limiting where it matters

Login, signup, password reset, “forgot password”, and any endpoint that sends an email or costs you money (hello, LLM calls) usually ship with zero rate limiting. That means brute-force attempts, credential stuffing, and bills that run up while you sleep.

How to check it yourself: put a basic limiter in front of auth and any expensive or outbound-email endpoint. Even a simple per-IP + per-account limit stops the cheap attacks that hit new apps first.

The pattern behind all five

Notice what these have in common: the app works. Every test passes. The dashboard is green. The bug only shows up when someone who isn’t you — a real user, or someone actively poking at it — interacts with it.

That’s exactly the gap automated checks can’t close. A scanner pattern-matches; it doesn’t know that this route is the admin route, or that this query crosses a tenant boundary. A person who reads your actual code does.

If you’re about to launch and any of these made you slightly unsure, that’s the signal to have someone read it first. That’s the whole idea behind a production-readiness audit: a senior engineer reads your code, finds the issues that hide behind green checkmarks, and tells you — in plain English — what to fix before real users and real data go behind it.