Vibe-Coding Security: What Goes Wrong in AI-Built Apps
Vibe-coded apps fail security in a predictable way: the AI builds a working UI on top of a Supabase database it never locks down. The same three holes show up over and over — Row Level Security left off, keys leaked into the browser bundle, and auth checked in React but never on the API. None of them break the app, so you ship them. The fix for all three is the same principle: treat every request to your database as hostile and gate it on the server.
"Vibe coding" — describing what you want to an AI tool like Lovable, Bolt, v0, or Cursor and shipping what it generates — is genuinely fast. It's also producing a wave of live apps with a recognizable security signature. When researchers measured AI-generated code, they found roughly 61% of it was functionally correct but only about 10.5% passed a security review. The code runs. It just isn't safe. And because the failure is silent, you find out the way everyone does: when someone else finds your data first.
What are the most common vulnerabilities in AI-generated apps?
The most common vulnerabilities in AI-generated Next.js + Supabase apps are missing Row Level Security, secrets leaked into the client bundle, and authentication enforced only in the UI. These dominate real-world exposures because they're the default output of how the tools work: the AI generates a schema and a polished frontend, but the database access policy — the part that actually protects data — is left to you, and you didn't know to ask for it.
This isn't a hunch. In October 2025, the security firm Escape scanned 5,600 publicly reachable vibe-coded apps and found more than 2,000 high-impact vulnerabilities, over 400 exposed secrets, and 175 instances of leaked personal data — including medical records and bank details — all in live production systems. Their finding was blunt: most of these issues sit in the open, retrievable without any privileged access at all.
The anchor incident: CVE-2025-48757
The clearest proof that this is structural, not a few sloppy users, is CVE-2025-48757. Disclosed in May 2025 by security researcher Matt Palmer and rated CVSS 9.3 (Critical) by NVD, it describes Lovable-built apps that shipped with missing or insufficient Supabase Row Level Security policies. Because Supabase exposes every table in the public schema through an auto-generated REST API — reachable with the anon key that ships in the browser bundle by design — any table without RLS was readable by anyone.
Palmer's scan found 303 endpoints across 170 production Lovable apps leaking data this way: user emails, phone numbers, payment status, and in some cases third-party API keys for services like Google Maps and the Gemini API. No login. No exploit chain. Just the documented Supabase API doing exactly what it was told, against a table nobody locked.
The point isn't that Lovable shipped a bug — every one of these apps would have had the same hole if a human had wired up Supabase the same way. The lesson is that the AI optimizes for "it works in the demo," and a missing RLS policy is invisible in the demo. It stays invisible right up until production.
The recurring failure modes (and the fix for each)
Across Lovable, Bolt, v0, and Cursor output, the same patterns repeat. Here's each one mapped to a concrete fix.
1. Tables created without Row Level Security
This is the CVE above, and it's the single most common failure. The AI runs create table to make your schema work, the dashboard query returns rows, everyone moves on. But a table without RLS is a public API endpoint — anyone with your project URL and the anon key (both public) can dump it:
curl "https://YOUR-PROJECT.supabase.co/rest/v1/profiles?select=*" \
-H "apikey: YOUR_ANON_KEY"
The fix: enable RLS on every table in public, then write the policy that grants the access your app actually needs.
alter table public.profiles enable row level security;
create policy "Users read own profile"
on public.profiles for select
to authenticated
using ( (select auth.uid()) = user_id );
Watch for the two adjacent traps the AI also falls into: disabling RLS as a "fix" for a 403 when the real problem is a missing policy, and writing a policy with using (true) that re-opens the table to everyone.
2. Secrets leaked into the client bundle
AI tools paste API keys wherever they need them to "make it work," and in a Next.js app that often means a NEXT_PUBLIC_ prefix — which inlines the value into the JavaScript bundle shipped to every visitor. The catastrophic version is the Supabase service_role key, which bypasses RLS entirely. One service_role key in a bundle is a full database compromise — read, write, delete every row, no policy in the way. The Escape scan above found Supabase service keys among the secrets retrievable straight from frontend bundles.
The fix: keep the service_role key server-only (no NEXT_PUBLIC_ prefix, never imported into a "use client" file), and use the server-only package as a build-time guardrail. The browser should only ever hold the anon key. And don't hardcode keys in source — they live in git history forever, where bots actively scrape for them. If a secret already shipped, rotate it; the exposed one is burned.
3. Auth checked in the UI, not on the API
This is the subtlest one. The AI generates a React app that hides the dashboard until you log in, so it looks protected. But the Supabase endpoints behind it are still open — an attacker skips the UI entirely and hits the API directly. The same gap shows up in Next.js Server Actions that don't re-check auth and API routes that trust their input.
The fix: authentication belongs on the server, on every data path. A hidden component is not access control. Pair RLS (which enforces per-user access at the database) with an explicit auth check in every Route Handler and Server Action. Treat the UI gate as cosmetics, not security.
Why these slip through every time
Three properties make vibe-coding vulnerabilities uniquely sticky:
- They're silent. No error, no failing test, no deploy warning. The app works perfectly, which is exactly why the hole survives to production.
- The tool optimizes against you. The AI's reward signal is "the feature works in the preview." Security controls make nothing visibly better in a demo, so they're the first thing skipped.
- You can't review what you didn't write. The whole appeal of vibe coding is not reading the code. That's fine for a landing page and fatal for the line that decides who can read your users' table.
The takeaway isn't "stop using AI tools" — they're too useful. It's that the AI builds the product and you own the security boundary, the same way you would with code a junior wrote at 2am. Someone has to check the database policies, the bundle, and the auth paths before it ships. If that someone is you reading every diff, great. If not, automate the check.
Verify before you ship
You can run the first-pass audit by hand in under a minute:
# Any table-creating migration without a matching RLS enable?
grep -rniE 'create table' supabase/migrations
# Any secret behind a public prefix?
grep -rnE 'NEXT_PUBLIC_.*(KEY|SECRET|TOKEN|SERVICE_ROLE)' .
# Which public tables have RLS off right now? (run in the SQL editor)
# select tablename from pg_tables where schemaname='public' and not rowsecurity;
Anything those turn up is a candidate for exposure. To catch all three failure modes on every push instead of by memory, run a free GuardLayer scan — it flags missing RLS, leaked keys, and unguarded auth across your Next.js + Supabase repo, posts the findings on the PR, and gates the merge before the hole reaches production.
FAQ
Is vibe-coded code less secure than hand-written code? On average, yes — not because AI writes worse code, but because the security boundary (RLS policies, key handling, server-side auth) is exactly the part that's invisible in a working demo, so it gets skipped. Research found roughly 10.5% of AI-generated code passed a security review even though most of it ran correctly.
What is CVE-2025-48757? A May 2025 vulnerability rated CVSS 9.3 (Critical) by NVD, in apps built with Lovable that shipped with missing or insufficient Supabase Row Level Security. Researcher Matt Palmer found 303 endpoints across 170 production apps leaking user data via the public anon key. The root cause was a missing RLS policy per table.
Which is the most dangerous vibe-coding mistake? Leaking the Supabase service_role key into the client bundle, because it bypasses every RLS policy you have — it's a full database compromise. Missing RLS is the most common; a leaked service_role key is the most severe.
Do I need to read all the AI-generated code to be safe?
No, but you do need to verify the security boundary: that RLS is on for every public table, no secret carries a NEXT_PUBLIC_ prefix, and auth is enforced on the server, not just hidden in the UI. A scanner can check these automatically on every push.
Does adding RLS break my app?
No — it changes "everyone can read this table" to "only the right user can." If a query starts returning empty after enabling RLS, that's the signal you were missing a policy; add one scoped with using ( (select auth.uid()) = user_id ) rather than disabling RLS.
Catch this before it ships — free
GuardLayer scans every push for this and 19 other Next.js + Supabase issues, with the exact fix inline.