PL-003lowLab

RLS enabled but no policies (owner locked out)

Not a leak, yet. The table is locked to everyone, and the quick fix people reach for is the leak.

Severity
Low
Who can exploit it
Nobody, yet
Found by
Free RLS check (static) and the full lab check
Lab proof
Automatic: the owner-still-reads check

What it is

With RLS switched on and no policies at all, Postgres denies every read and write through the API. Nothing leaks. But the app's own users can't see their data either, so a feature is broken.

It matters because of what usually happens next. Someone asks their AI tool to "make it work", and the fastest change that makes the error go away is using (true), which turns a broken table into PL-001. The other common workaround is putting the service role key into the frontend, which is PL-010.

Example of vulnerable code

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

supabase/migrations/…_create_notes.sql
create table public.notes (
  id bigint generated always as identity primary key,
  user_id uuid not null references auth.users (id) on delete cascade,
  body text
);
alter table public.notes enable row level security;
-- ...and no create policy statements for public.notes anywhere

Check it yourself

check-pl-003.sql
-- Tables with RLS on and no policies at all
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 c.relrowsecurity
  and not exists (
    select 1 from pg_policies p
    where p.schemaname = 'public' and p.tablename = c.relname
  );

The fix PatchLabs writes

PatchLabs writes the four owner policies the table was missing, so users get their own rows back and nobody else gets anything.

supabase/migrations/…_patchlabs_fix.sql
create policy "Owners read own notes" on public.notes
  for select to authenticated using ((select auth.uid()) = user_id);
create policy "Owners add own notes" on public.notes
  for insert to authenticated with check ((select auth.uid()) = user_id);
create policy "Owners edit own notes" on public.notes
  for update to authenticated
  using ((select auth.uid()) = user_id)
  with check ((select auth.uid()) = user_id);
create policy "Owners delete own notes" on public.notes
  for delete to authenticated using ((select auth.uid()) = user_id);

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.notes
[BEFORE]  owner     → 0 rows   (locked out)
[FIX]     add owner policies
[PROVE]   anon      → 0 rows        DENIED
          stranger  → 0 rows        DENIED
          owner     → 1 row, own    OK
[DONE]    lab proof passed
provenIllustrative output for the example above · fake rows

The owner check is the part of the proof that catches this. A fix that leaves the owner with 0 rows fails, which is also how the lab catches fixes that close a leak by locking everyone out.

When the pattern is intended

RLS on with no policies is the correct setup for a table only your server touches: an audit log, a job queue, or data written by an Edge Function using the service role key. The service role bypasses RLS, and the API roles get nothing, which is what you want.

If your frontend never reads the table, there is nothing to fix. The free RLS check reports it as low severity for that reason.

Supabase linter

0008 rls_enabled_no_policy
Flags the same pattern. Supabase's linter reports it; PatchLabs adds the owner policies and proves the owner can read again.

Guides

Free check

Does your app have PL-003?

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.