A vibe-coded Supabase app usually ships with a publishable API key in the browser. That is fine.
It is only fine if every exposed table has RLS on, leftover grants revoked, and policies you have tested as a second user.
Most AI-built apps miss that third part.
The failure mode
Lovable, Bolt, Cursor, and v0 will happily create tables and a client. They will not reliably:
- enable RLS on every table the client can see
- stop using the
service_rolekey in Vite - write policies that fail closed for
anonandauthenticated - prove a second account cannot read the first account's rows
The publishable key is not the leak. The open table is the leak.
The five-minute test
- Open the live app.
- Create two users.
- As user B, request user A's rows with the same client the app uses.
- If you get data back, you do not have a product. You have a public database.
If service_role or a VITE_ secret is in the bundle, rotate first. RLS cannot save you while that key is public. I wrote that check here: Is your Lovable app leaking the service_role key?
What to do before the next 50 users
Enable RLS. Revoke grants you do not need. Write policies per table. Test as a stranger. Move privileged writes to Edge Functions.
Do not start with a rewrite.
The longer checklist is here: Supabase RLS for vibe-coded SaaS.
Top comments (1)
The two-user test gets straight to the failure: user B querying user A's rows through the shipped client can expose a gap that a working UI hides. I'd make that a migration gate for every browser-exposed table, checking cross-account reads and updates while confirming owners can still access their own data. Rotating a
service_rolekey exposed in a Vite bundle comes first, since RLS cannot protect data while that key is public. The tradeoff is a little more test setup for a much clearer release decision.