If Supabase says a table is “unrestricted”, or the Security Advisor warns that RLS is disabled, anyone who visits your site may be able to read that table, and maybe change it. Here is what that means and how to fix it, usually in a few minutes.
What RLS is, in one paragraph
Apps built on Supabase, including most Lovable and Bolt apps, read the database straight from the visitor’s browser. To do that, the site ships a public key (called anon, or a publishable key starting sb_publishable_). Anyone can copy that key from your site, and that is fine by design. What stops them reading everyone’s data is Row Level Security: rules on each table, called policies, that decide which rows a request may see or change. With RLS off, the public key can do whatever the table’s permissions allow, which on most existing Supabase tables is read, insert, update and delete. (From 30 May 2026, Supabase began rolling out a change so that new projects no longer give the public key access to new tables automatically, and existing projects change on 30 October 2026. Tables you already have keep their access.) Built with Lovable? Our guide Is my Lovable app secure? covers the other common problems too.
Step 1: find tables without RLS
Any one of these works:
- Table Editor: tables without RLS are marked as unrestricted.
- Security Advisor (under Advisors in the Supabase dashboard): lists every public table with RLS disabled.
- SQL Editor: run this query.
select tablename from pg_tables where schemaname = 'public' and rowsecurity = false;
Treat every table it lists as open to the public key. To see the policies you already have, run:
select tablename, policyname, cmd, roles, qual, with_check from pg_policies where schemaname = 'public';
Step 2: turn RLS on
For each table, run this in the SQL Editor (change notes to your table name):
alter table public.notes enable row level security;
As soon as RLS is on, the public key can no longer read or change anything in that table until you add a policy. That is the safe default, but it also means parts of your app will stop working until Step 3. Do the two steps together.
Step 3: add policies
Most tables in an app hold rows that belong to one user, with a column such as user_id. These four policies let each signed-in user see and change only their own rows. This is the pattern Supabase’s own documentation uses.
create policy "Users can read their own notes" on public.notes for select to authenticated using ( (select auth.uid()) = user_id ); create policy "Users can add their own notes" on public.notes for insert to authenticated with check ( (select auth.uid()) = user_id ); create policy "Users can update their own notes" on public.notes for update to authenticated using ( (select auth.uid()) = user_id ) with check ( (select auth.uid()) = user_id ); create policy "Users can delete their own notes" on public.notes for delete to authenticated using ( (select auth.uid()) = user_id );
Three details matter. using decides which existing rows a user can touch. with check decides what the row may look like afterwards, so a user can’t hand a row to someone else or take one. If you leave it out, Postgres uses the using rule for both, but writing it out makes the rule clear, and it must never be looser than using. And to authenticated means signed-out visitors get nothing at all.
On a newer Supabase project, a new table also needs permission before your app can reach it at all. If you see a “permission denied” error (code 42501), run this. Add the service_role line if server code such as an Edge Function uses the table, and only grant to anon for tables that really are public:
grant select, insert, update, delete on public.notes to authenticated; grant select, insert, update, delete on public.notes to service_role;
For a table that really is public, such as a list of blog posts, allow reading only:
create policy "Anyone can read published posts" on public.posts for select to anon, authenticated using ( published = true );
Using Lovable or Bolt? Ask it to do this
You can paste this into your AI tool instead of writing the SQL yourself, then check the result with the query from Step 1.
Enable Row Level Security on every table in the public schema. For each table that stores user data, add select, insert, update and delete policies limited to the row owner, using (select auth.uid()) = user_id, with a with check clause on insert and update. For tables that must be public, allow select only. Do not use using (true) on any table with private data. Then list every table with its policies.
Mistakes that leave the door open
using (true)on private data. RLS is on, but the policy lets everyone in. AI tools sometimes add this to make an error go away.- Trusting a role the user can edit. A policy that checks
is_adminon the user’s own profile row is only safe if users can’t update that column. - The secret key in the browser. The
service_rolekey, or a key startingsb_secret_, skips RLS entirely. It belongs only in server code such as Edge Functions, never in front-end code orVITE_orNEXT_PUBLIC_settings. - Views. A view in Postgres runs with its owner’s rights by default, so it can show rows that the table’s policies would hide. On Postgres 15 and later, create views
with (security_invoker = true). - Storage. Files in Supabase Storage have their own rules. Private uploads belong in a private bucket with policies on
storage.objects.
How to know it worked
Run the Step 1 query again: it should return nothing. Before launch, work through the security checklist for AI-built apps too. Then sign in as one test user and check you can’t see another test user’s data. Holefind’s optional live site check can also test, with your permission, which of your tables can be read with your site’s public key. It sends count-only, read-only requests and never reads the contents of any row. How the site check works.
Questions
Is it safe that my Supabase anon key is visible in my site’s code?
Yes, that key is designed to be public. It is only safe when Row Level Security is on for every table and the policies limit each row to the right people. The service role or secret key is the one that must stay private.
Will turning on RLS break my app?
It can, for a moment. With RLS on and no policies, the app can’t read that table at all. Add the policies at the same time as you enable RLS and the app keeps working, with each user seeing only their own data. On a newer project, a new table may also need the grant shown above.
Does Supabase turn RLS on for new tables?
Don’t rely on it. Tables created with SQL or by an AI tool’s migration often don’t have it, and Supabase’s documentation says RLS must be enabled on every table in an exposed schema. Check with the query above whenever you add tables.