183 out of 200 apps carried a security hole. That's the ratio GuardMint pulled from its internal audit of vibe-coded apps in Q1 2026, and there's a decent chance yours is sitting in that pile too. Not some stray log file forgotten in a corner. A flaw traced straight back to a model hallucination, or a security context nobody thought to hand the prompt.
The clearest example landed in February. An education app published on Lovable's Discover page (the kind of feed that surfaces "look what I built" projects to the whole platform) exposed 18,000+ accounts. Students and institutions, including UC Berkeley and UC Davis, sat in that pile. No row level security, no role-based access control, and an authentication function so broken it locked out legitimate users while leaving unauthenticated attackers free to delete accounts and edit grades.
This isn't a one-off. Symbioticsec scanned 1,072 vibe-coded apps sitting on Supabase and found flaws in 98% of them, 16% critical. An academic team combing through 1,471 vulnerabilities in publicly deployed vibe-coded apps landed on 90%, more than 75% rated severe or critical. 3 different methodologies, 3 numbers within spitting distance of each other. That looks less like an anecdote and more like a structural pattern.
The Policy Is the Only Wall Left
The public key isn't the bug. On Supabase, your client queries the database directly with an anon key, sitting in plain view inside your JS bundle for anyone with dev tools open. That's by design. The key is meant to be public.
What isn't supposed to be public is what happens after the key gets used. That's where Row Level Security (RLS for short) comes in. Think of RLS as a WHERE clause the database bolts onto every single query automatically, based on who's asking, invisible to the client and enforced entirely on the Postgres side no matter what the front end thinks it's allowed to see.
Write the policy right, and a user only ever gets rows they own, no matter how the query is phrased or which client sends it. Skip the policy entirely, or write it as USING (true) (which looks harmless in a rushed prompt session but reads as "always true, always allow" to Postgres), and that WHERE clause effectively disappears. Every request comes back with every row, the anon key doing exactly what it was always able to do once nothing stood in its way.
This exact trap runs through how a Lovable Cloud migration exposed its own database, and it's worth recognizing before it's your data on the wrong side of that WHERE clause.
Lovable ships a built-in security scanner, and it checks for 1 thing: does a policy exist on this table. Not whether the policy actually restricts anything. A table locked down with USING (true) sails through the scan looking secure while sitting wide open. Picture walking into a boss room and the boss just isn't there (no cutscene, no warning, just an empty room and a chest with everything in it). That gap isn't a Lovable-specific bug either, it's shared by any stack that lets the browser talk to Postgres directly instead of routing through a server that enforces its own checks.
I think the scanner isn't lying exactly, it's just answering the wrong question. Maybe I'm reading too much into it, but that distinction is going to matter more as these audit badges optimize for marketing before they optimize for security.
Row Level Security either holds or it doesn't. There's no easy mode.
3 Prompts, 20 Minutes

Prompt 1, in Claude Code, list what actually exists:
List every table in this Supabase project along with its RLS policies, including tables that have RLS enabled but zero policies attached.
Run it before touching anything else. The gap you're looking for isn't "RLS disabled." It's a table with RLS turned on and nothing written under it, which looks safe in the dashboard and behaves exactly like no RLS at all.
Prompt 2, prove it from outside your own login:
curl "https://YOUR_PROJECT.supabase.co/rest/v1/customers?select=*" \
-H "apikey: YOUR_ANON_KEY"
No auth header, no session, nothing but the public key any visitor already has. If this comes back with rows, you've just reproduced the exposure without needing a hacker's toolkit.
Prompt 3, test 3 identities:
Use Supabase Studio's impersonation feature, or pgTAP (a scripted testing framework for Postgres) if you want it automated, and run the same query as an unauthenticated visitor, then as the account owner, then as someone else entirely. Log in as Karen from Accounting and see if she can read the CEO's invoices. If Karen can, your policy has a hole shaped exactly like Karen.
That's the core loop. Once you're in there, a few more things live right next to it:
- DELETE and PATCH endpoints with zero identity verification. Symbioticsec found 172 sites allowing both, wide open.
-
service_rolekeys sitting client-side. This is the anon key's final boss form (full bypass, no policy checked at all). - Hardcoded secrets baked into the JS bundle. Search your build output for
API_KEY,SECRET, anything starting withsk_. - RLS missing specifically on tables added later. The first table in a project usually gets a policy. The 5th table, bolted on 6 weeks later, often doesn't.
Supabase's own Security Advisor catches some of this automatically. Decent first pass, not a substitute for the 3 prompts above, since it shares the same blind spot as Lovable's scanner.
Not a Lovable Problem
None of this is specific to 1 platform. Bolt.new ships RLS off by default, full stop. v0 and Replit Agent hit the same wall the moment they wire up a database without being told explicitly to add access rules. Cursor plus Supabase has the identical failure mode, just with a human typing the prompts instead of an agent chaining them. Swap the logo, and you get the same empty boss room. The common thread isn't which tool wrote the code. It's a client that talks straight to Postgres, with nothing standing between the browser and the table except whatever policy someone remembered to write.
If you're building on Lovable specifically, a survival guide for shipping on Lovable is worth a read once the immediate fire is out.
Somewhere in the middle of writing this, my own Supabase dashboard threw a stale connection warning for the 3rd time this week (unrelated to anything here), and I still haven't filed the support ticket. Some fires you fight, some you just tab away from.
Worth saying plainly: if what's behind that anon key is health records, payment details, anything regulated, the 20 minutes above gets you a diagnosis, not a cure. It tells you where the hole is. Patching it properly, and proving it stays patched, is a different job. Past a certain sensitivity level, that job belongs to someone doing a real audit, not a prompt.
Shipping Is Free. Securing Isn't a Reflex Yet.
Vibe coding made shipping free. A single prompt gets you a deploy, and the app is live before lunch. What hasn't caught up at the same pace is the reflex to secure it, the kind of caution that used to get bolted on by default and now has to be asked for explicitly, every single time.
The mechanism stays the same everywhere the client talks straight to the database. Lovable, Bolt, v0, Cursor plus Supabase (doesn't matter which logo is on the tool). The public key stays public, and the policy is the only thing standing between an anonymous request and the whole table.
The 3 prompts are up there. 20 minutes, no excuse left. What's left is whether you'd rather run them yourself or wait to show up as a case study in next quarter's audit.
Sources
- GuardMint internal audit, Q1 2026 (via secure.com)
- Symbioticsec, scan of 1,072 vibe-coded apps, April 2026
- Superblocks / Matt Palmer, CVE-2025-48757, March 2026
- Rankiteo, Lovable education app exposure, February 2026
- PTKD Journal, Lovable/Supabase RLS bypass mechanism, May 2026
- Unico Connect, Supabase RLS Security Checklist 2026
- "Understanding the (In)Security of Vibe-Coded Applications," arxiv
This post may contain affiliate links. If you click them, I might earn a small commission — costs you nothing, and helps me keep shipping quality articles every day for your reading pleasure.
Top comments (0)