Vibeproof / Guides · Privacy

Is my Supabase data public? How to check Row Level Security in 5 minutes

If you built your app with Lovable, Bolt, Cursor, Replit or v0 and it uses Supabase, your database might be readable, or even deletable, by anyone. Here's how to check, and how to fix it.

Updated October 15, 2026 · 7 min read

The short version

Why this happens so often with AI-built apps

Supabase gives every project an automatic API. Your app talks to it using a public key that ships in your website's JavaScript, so anyone can find it. That's by design: the key only says "I'm a visitor." What decides what a visitor can actually see is Row Level Security, a set of rules on each table.

AI app builders often create tables quickly and either skip RLS or add a rule that says "everyone can do everything" just to make an error go away. The app works, so nobody notices. In a June 2026 scan of 1,072 live vibe-coded Supabase apps, about 1 in 6 let strangers delete or modify data without logging in (Symbiotic Security).

Which keys are safe to be public?
✅ sb_publishable_... or the legacy anon key: fine in browser code.
❌ sb_secret_... or the legacy service_role key: never in browser code. They bypass RLS completely. If one is in your site, jump to our leaked key guide first.

Step 1: Find tables with RLS turned off (1 minute)

In your Supabase dashboard, open SQL Editor, paste this, and click Run:

select tablename, rowsecurity
from pg_tables
where schemaname = 'public'
order by rowsecurity, tablename;

Any row where rowsecurity is false is a table with RLS turned off. On most projects, Supabase's default permissions mean a public table with RLS off can be read and changed by anyone holding your public key. Treat every false as exposed until you've fixed it.

You can also open Advisors → Security Advisor in the dashboard. It flags tables in the public schema without RLS, along with other common mistakes.

Step 2: Check the rules you already have

RLS being on isn't enough if the rules are too loose. List every policy:

select tablename, policyname, cmd, roles, qual, with_check
from pg_policies
where schemaname = 'public'
order by tablename;

Red flags to look for:

What you seeWhat it means
qual or with_check is just trueEveryone passes. Common "make the error go away" fix from AI tools.
roles includes {anon} or {public} on insert, update or deleteLogged-out visitors can change data.
An update policy with no with_checkUsers may be able to change a row so it belongs to someone else.
A role, is_admin, plan or credits column in a table users can update"Users can update their own row" also lets them update that column, so anyone can make themselves admin or give themselves credits. See below.
Rules reading raw_user_meta_data / user_metadataUsers can edit their own user metadata. Never use it to decide permissions like "is admin."

Step 3: Turn RLS on and add safe rules

For a typical table where each row belongs to one user (a user_id column), this is the standard pattern from the Supabase docs. Replace profiles and user_id with your table and column:

alter table public.profiles enable row level security;

create policy "Users can view their own rows"
on public.profiles for select
to authenticated
using ( (select auth.uid()) = user_id );

create policy "Users can create their own rows"
on public.profiles for insert
to authenticated
with check ( (select auth.uid()) = user_id );

create policy "Users can update their own rows"
on public.profiles for update
to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );

create policy "Users can delete their own rows"
on public.profiles for delete
to authenticated
using ( (select auth.uid()) = user_id );

Wrapping auth.uid() in (select ...) is a Supabase-recommended speed trick, not a typo. Also add an index on user_id if the table is large.

Turned RLS on and now your app shows nothing? That's expected. RLS with no policies blocks everyone. Add the policies above and data will reappear for the right users only.

Prefer to let your AI tool do it? Paste this prompt

Audit my Supabase database security. For every table in the public schema:
1. Enable Row Level Security if it's off.
2. Replace any policy that uses "true" or allows the anon role to insert, update or delete.
3. Add policies so signed-in users can only select, insert, update and delete rows where user_id = (select auth.uid()). Updates need both "using" and "with check".
4. Don't use user_metadata in any policy, and make sure users can't update columns like role, is_admin, plan or credits on their own rows.
5. For any view, set security_invoker = true.
List every change you make and any table where you weren't sure who should have access.

The "make yourself admin" trap

A very common real-world bug: the profiles table holds both the user's name and their role, with a rule that says "users can update their own profile." That rule checks whose row it is, not which columns change, so a user can set their own role to admin.

Fix it by only allowing updates to safe columns (replace the column names with your own):

revoke update on public.profiles from authenticated;
grant update (full_name, avatar_url) on public.profiles to authenticated;

Even better, keep roles, plans and credits in a separate table that users can't write to at all, and change them only from server-side code.

Step 4: Don't forget views and storage

Step 5: Test it like a stranger would

Create two test accounts. Log in as account A, create some data, then log in as account B and try to view or change A's data through your app. If B can see it, your rules are still too loose. Only test apps you own.

Not sure if your app leaks?

Vibeproof's free scan checks your live app for exposed secret keys, a detected Supabase backend, missing protections and public source code in under a minute.

Scan my app free