Supabase security
Supabase RLS scanner
Your Supabase anon key is public by design. Row-Level Security is the only thing stopping someone who copies it from your site from reading, or rewriting, every row in your tables. VibeSafe scans your migrations and app code for the RLS mistakes AI builders ship, and explains each fix.
Paste your SQL migrations and the files that query Supabase · No signup to try
Why this matters more in AI-built apps
Lovable, Bolt and similar tools wire the browser straight to Supabase. That's fine when RLS is on and scoped correctly, and a data leak when it isn't. In our 400-app study, 35.2% of the repositories used Supabase, which makes RLS the single most important setting for a large share of vibe-coded apps.
The six RLS mistakes the scanner looks for
- 1. A table with no RLS at all. Created in a migration, queried from the client, never given
enable row level security. Anyone with the anon key can read every row. - 2. RLS on, but a policy that lets everyone in.
using (true)on a table holding user data switches RLS on in name only. - 3. Reads protected, writes not. A SELECT policy scoped to the owner, but UPDATE or DELETE left broad. Users can't see each other's rows but can change or delete them.
- 4. INSERT or UPDATE without
with check. A user can write a row that claims to belong to someone else. - 5. Filtering only in app code.
.eq('user_id', user.id)in the frontend looks like protection, but anyone can call the table without that filter. - 6. A service_role key in the browser. The service role bypasses RLS completely, so every policy above stops mattering.
What a finding looks like, and the fix
A typical AI-generated migration:
create table notes (
id uuid primary key default gen_random_uuid(),
user_id uuid references auth.users not null,
body text
);
-- no RLS: every row readable and writable with the anon key
The fix, scoped for every operation:
alter table notes enable row level security;
create policy "owner can read" on notes for select using (auth.uid() = user_id);
create policy "owner can insert" on notes for insert with check (auth.uid() = user_id);
create policy "owner can update" on notes for update using (auth.uid() = user_id) with check (auth.uid() = user_id);
create policy "owner can delete" on notes for delete using (auth.uid() = user_id);
Check your live project by hand (2 minutes)
The scanner reads code; this confirms what your deployed database actually allows. Take the project URL and anon key from your site's network requests, then, on your own project only:
curl "https://YOUR-PROJECT.supabase.co/rest/v1/notes?select=*" \
-H "apikey: YOUR_ANON_KEY" \
-H "Authorization: Bearer YOUR_ANON_KEY"
If that returns other users' rows while logged out, RLS isn't protecting that table. An empty array [] is what you want. Repeat for each table that holds user data. The Supabase dashboard's security advisor also lists tables with RLS disabled.
What the scanner does not do
- It doesn't connect to your database, so policies created only in the dashboard and never saved to a migration aren't visible to it. Use the manual test above.
- It doesn't flag your anon or publishable key. Those are public by design.
- It can't know your business rules, such as whether team members should see each other's rows. It flags policies that look too broad and explains why; you decide.
Questions
What does a Supabase RLS scanner check?
It looks for tables reachable from the browser without Row-Level Security, RLS enabled with policies that allow everyone, policies covering SELECT but not UPDATE or DELETE, INSERT and UPDATE policies without WITH CHECK, user filtering done only in app code, and a service_role key in client code that bypasses RLS entirely.
Can VibeSafe read my live Supabase policies?
No. VibeSafe scans code: your SQL migrations, schema files and the app code that queries Supabase. It does not log in to your database. Use the manual test on this page to confirm what your live project actually allows.
Is the Supabase anon key a security problem?
No. The anon or publishable key is designed to be public, which is exactly why RLS matters: anyone holding it can call your tables directly, and RLS limits what they get back. VibeSafe does not flag the anon key; it flags a service_role key in client code.
I enabled RLS and my app stopped returning data. Is that normal?
Yes. Enabling RLS denies everything until you add policies. Add a policy for each operation the app needs, scoped to the owner, for example auth.uid() = user_id.
Related: