← Blog/Security/Sep 20, 2026 · New

You're Doing Vibe-Coding Wrong. Here's the 17-Point Fix.

Omenabyte Intelligence·Sep 20, 2026·9 min read
Vibe-coded app security — RLS off, exposed API keys, open database endpoints

Nobody tells you this about the app you shipped last weekend: it's probably leaking.

Not "might be" leaking. Probably leaking right now, to anyone who opens DevTools and spends four minutes looking. That isn't a scare tactic. Multiple independent scans run over the past year against thousands of AI-built apps landed on the same conclusion, and the details get worse the further you read.

What the scanners actually found

Three research groups scanned thousands of AI-generated web apps built with Lovable, Bolt, v0, Replit, and Cursor. The numbers line up uncomfortably well.

Vibe App Scanner ran 1,215 scans across 1,119 distinct apps between December 2025 and August 2026. 10.4% had at least one critical issue. 31.1% had something critical or high severity. Of the 359 apps using Supabase, which was 29.5% of the sample, 141 of them had a row-level-security or data-exposure problem that let information be read without authorization. That's 39.3%.

Symbiotic Security crawled 65,643 URLs, confirmed 1,085 Supabase-backed sites, and scanned 1,072 of them. They logged 6,185 vulnerabilities. 98% of sites had at least one. 16% had a critical one. Only 26 of the 1,072 came back completely clean.

Worth stating plainly: that 98% applies to Supabase-backed apps in their sample, not every AI-built app everywhere. And Vibe App Scanner flags its own bias in the other direction. Their apps were voluntarily submitted by owners who already suspected a problem, so those rates over-represent risk, and 83% of their scans were the reduced quick-scan set. Their words: every prevalence number is a floor, not a ceiling. Read the two studies together and the honest read is "unusually bad, uncertain how bad."

Two percent clean. That's the rate in the larger sample.

The specific failures

  • 172 sites let anyone delete records from the database without logging in. One DELETE request using the public key wipes entire tables.
  • 39 sites had tables fully readable by anyone holding the Supabase anon key, which is embedded in the page's JavaScript by design. So "holding it" just means "viewed source." Among those tables: payments, admin_users, and chat_messages.
  • 308 sites exposed the anon key in JavaScript. On its own that's expected, it's a public key. The problem is what happens when no RLS policy exists behind it.
  • 34 sites had columns containing emails, hashed passwords, auth tokens, and phone numbers, all directly queryable through the Supabase REST API.

None of this is theoretical. CVE-2025-48757 is the one that made headlines. A researcher scanned 1,645 apps from Lovable's official showcase, found 170 with critical flaws, and the same root cause kept showing up. Missing row-level security. MITRE scored it CVSS 9.3 Critical, though Lovable disputes the rating on the grounds that each customer is responsible for their own app's data, and the researcher later published his own scoring at 8.26. NVD lists the record as disputed. The vulnerability itself, insufficient RLS in generated sites through April 2025, isn't in dispute.

Separately, a February 2026 report found an EdTech app with 16 vulnerabilities, 6 of them critical. 18,697 user records exposed, including 14,928 unique emails and 4,538 student accounts from K-12 schools plus UC Berkeley and UC Davis. The auth logic was inverted. It blocked logged-in users and let anonymous visitors straight through.

Then there's Moltbook, an AI-agent social network. Wiz Research found zero RLS on any table. 1.5 million API tokens for OpenAI and Anthropic, 35,000 email addresses, and private messages, all reachable with the public key.

Why this keeps happening

Supabase turns row-level security off by default when you create tables through SQL. The Dashboard Table Editor defaults to on, but AI coding tools generate SQL migrations. They don't click through a Dashboard. So every table the AI creates starts unprotected.

There's a documented pattern for what follows, called the fix loop. Palo Alto's Unit 42 described it. The AI writes a query, PostgreSQL returns error 42501 (insufficient privileges, which is RLS doing exactly its job), and the AI "fixes" the error by dropping the RLS policy. The query works. The table is now open. Nobody notices, because the app appears to function.

Carnegie Mellon measured the underlying gap. 61% of AI-generated code is functionally correct, but only 10.5% is secure. Auth code is where that difference draws blood.

It isn't only Supabase either. 38 apps across all platforms shipped hardcoded API keys in their JavaScript bundles. On Bolt.host, 17 of 251 apps. On Vercel's AI-generated apps, 18 of 67, which is 26.9%. One Replit app shipped Anthropic, OpenAI, and Google keys at the same time. Those keys bill per token. A leaked OpenAI key powering a loop can burn hundreds of dollars overnight.

Security headers are missing almost everywhere. 93.6% of scanned apps had no Cross-Origin-Resource-Policy. 78.4% had no Content-Security-Policy. 70.6% had no X-Frame-Options.

The common thread, as one report put it: AI code generators optimize for "does it work?" and not "is it safe?" Your prompt never said "add auth middleware to every endpoint" or "never embed API keys client-side," because neither is a functional requirement. The code works perfectly in a demo and fails catastrophically in production.

The 17-point checklist

Work through this list. It applies whether you built with Lovable, Bolt, v0, Cursor, Replit, or by hand.

  1. Protect admin routes. Every admin path needs an auth check on the server, not a hidden link or a client-side conditional. If the route renders, the route is reachable.
  2. Server-side permissions. Authorization decisions belong on the server. Anything the browser enforces, the browser can edit.
  3. Enable RLS. Run ALTER TABLE your_table ENABLE ROW LEVEL SECURITY; for every table in the public schema, then ALTER TABLE your_table FORCE ROW LEVEL SECURITY; so policies can't be bypassed. RLS with no policies locks the table entirely, which is safe but probably not what you intended. Write policies scoped to the row's owner, not to "any authenticated user."
  4. Verify email addresses. A confirmation flow that doesn't confirm anything is decoration. Turn it on and test it.
  5. Hash passwords securely. bcrypt, scrypt, or Argon2. Never SHA-256, never MD5, never plaintext.
  6. Keep tokens out of local storage. Any script on the page can read localStorage. Session tokens belong in httpOnly, Secure, SameSite cookies.
  7. Server-side API secrets. No keys in client code. Check your NEXT_PUBLIC_ and VITE_ variables specifically, because those prefixes mean "bundle this into the JavaScript that ships to every visitor." A service_role key in client code bypasses every RLS policy you wrote.
  8. Parameterized SQL queries. Never concatenate user input into a query.
  9. Validate form inputs. Server-side, with a schema. Client-side validation is a UX feature, not a security control.
  10. Block cross-site scripting. Escape output, avoid dangerouslySetInnerHTML with untrusted content, set a Content-Security-Policy.
  11. Validate file uploads. Check type and size, and never trust the filename. Don't serve uploads from the same origin as your app.
  12. Verify webhook signatures. An unverified webhook endpoint accepts forged events from anyone who guesses the URL.
  13. Rate limit requests. Login, signup, password reset, and anything that sends email or costs money.
  14. Tighten CORS settings. No wildcard origins on authenticated endpoints. Origin reflection with credentials enabled means any website can make authenticated requests on a logged-in user's behalf.
  15. Disable production debugging. Debug endpoints, verbose stack traces, GraphQL introspection, and source maps should all be off.
  16. Update dependencies. Run the audit, fix what it flags.
  17. Run an actual security review. Anthropic open-sourced claude-code-security-review under MIT. It's a GitHub Action that comments findings on your PRs, and Claude Code ships a /security-review slash command that does the same analysis. One caveat worth knowing: the action is not hardened against prompt injection, and Anthropic's own README says to use it only on trusted PRs. Set your repo to "require approval for all external contributors" first.

Two things people get wrong

The checklist is worth nothing if you skip these.

Confirm your .env files are actually excluded from Git, not just that they should be. Run git check-ignore .env and make sure it returns a match. If a secret ever reached a commit, rotating it is the only real fix. Rewriting history is cleanup, not a cure.

Then confirm sensitive data stays out of your logs. Logs get shipped, aggregated, and read by more people than you think. Never log tokens, passwords, full request bodies, or PII. Check your error handlers specifically, because that's where request objects get dumped wholesale.

Where this leaves you

Your app probably works. That's the part AI genuinely got good at. The security defaults underneath it are a different story, and the pattern across thousands of scanned apps says most of them haven't been touched.

None of this is expensive or exotic to fix. RLS is a policy on a table. Keeping a key server-side is moving a file. A CSP header is a config line. A large share of the exposures found in these scans were one default away from not existing.

Go check your tables. Start with the ones holding user data.

Omenabyte Intelligence · Service 09

We do this for a living.

Penetration testing, red teaming, bug hunting, and code audits. If you shipped a vibe-coded app and want to know what's actually exposed before someone else finds out, that's the work.

Originally published on omenabyte.com → https://omenabyte.com/blog/vibe-coded-app-security-checklist