PL-008critical (high without personal data)Lab

Row level security not enabled

No rules at all. With API access granted, anyone with the public key can read and change every row.

Severity
Critical (high without personal data)
Who can exploit it
Anyone on the internet
Found by
Free RLS check (static) and the full lab check
Lab proof
Automatic: anon, stranger and owner reads

What it is

Without RLS, a table has no row-level rules. If the API roles (anon, authenticated) have been granted access to it, anyone holding your public key can read, insert, change and delete every row through the Supabase API. The free RLS check rates it critical when the table has personal columns, high otherwise.

Supabase changed its defaults in 2026: new tables in public are no longer exposed to the API automatically (the default for new projects from 30 May, enforced on all projects from 30 Oct 2026, with existing grants kept). Older tables, and any migration that grants access explicitly, are still exposed.

Of the 70 rebuilt apps (22% of 318) where anyone with the public key could read personal data, this pattern and PL-001 were the two routes (PatchLabs lab study, n=318, Nov 2026).

Example of vulnerable code

Simplified from the patterns we see in AI-built apps. Synthetic: not taken from any real app.

supabase/migrations/…_create_leads.sql (vulnerable)
create table public.leads (
  id bigint generated always as identity primary key,
  name text,
  email text,
  phone text,
  created_at timestamptz default now()
);
-- No "alter table public.leads enable row level security;"
grant all on public.leads to anon, authenticated;

Check it yourself

check-pl-008.sql
-- 1. Tables in public with RLS switched off
select c.relname as table_name
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public' and c.relkind = 'r' and not c.relrowsecurity;

-- 2. Can the API roles reach them?
select table_name, grantee, string_agg(privilege_type, ', ') as privileges
from information_schema.role_table_grants
where table_schema = 'public' and grantee in ('anon', 'authenticated')
group by table_name, grantee
order by table_name;

The fix PatchLabs writes

PatchLabs switches RLS on and adds back exactly what the app uses. Here the public contact form keeps working and only admins can read the leads.

supabase/migrations/…_patchlabs_leak_fix.sql
alter table public.leads enable row level security;
-- The contact form can still submit
create policy "Anyone can submit a lead"
  on public.leads for insert
  to anon, authenticated
  with check (true);
-- Only admins can read them
create policy "Admins read leads"
  on public.leads for select
  to authenticated
  using (exists (
    select 1 from public.user_roles r
    where r.user_id = (select auth.uid()) and r.role = 'admin'
  ));

Every fix arrives as a migration file (plus any code change it needs, noted in the report) for your engineer to approve. Download it, or commit it on GitHub in one click from the report page. Automatic pull requests need our GitHub App, which is coming.

The proof it runs

lab proof · public.leads
[BEFORE]  anon      → 3 rows        LEAK
          email •••••@••••• phone +•• ••••
[FIX]     enable RLS + scoped policies
[PROVE]   anon      → 0 rows        DENIED
          stranger  → 0 rows        DENIED
          admin     → 3 rows        OK
[DONE]    lab proof passed
provenIllustrative output for the example above · fake rows

Here the "owner" is the admin who is meant to read the leads; the proof checks that the right person keeps access, not just that everyone else is locked out. The lab rebuilds with Supabase's default grants, so it sees the same exposure the live API would.

When the pattern is intended

Strictly, a table with RLS off is safe if the API roles have no grants on it, for example a table only server code uses. Switch RLS on anyway: it costs nothing, and it keeps the table closed if someone adds a grant later. If you must leave it off, the report notes the reason and stops alerting once you confirm.

Supabase linter

0013 rls_disabled_in_public
Flags tables in public without RLS. It reports an error level.
0007 policy_exists_rls_disabled
Flags tables that have policies written but RLS switched off, so the policies do nothing.

Guides

Free check

Does your app have PL-008?

Paste your public repo. We rebuild the database from your migrations, test it as a visitor, a stranger and the owner, and hand you the proven fix.

Free for your first appReads your public repo onlyOr run the free static RLS check

Last reviewed 10 Nov 2026. Finding IDs are stable: link to them freely. Spotted a mistake? Tell us.