Any signed-in user can read every row
Signing up unlocks other people's rows. If sign-up is open, that means anyone.
- Severity
- High
- Who can exploit it
- Anyone who signs up
- Found by
- Free RLS check (static) and the full lab check
- Lab proof
- Automatic: anon, stranger and owner reads
What it is
The policy checks that someone is signed in, but not that the row is theirs. It usually looks careful (to authenticated, or auth.uid() is not null), which is why it survives review. But if your app lets anyone create an account, being signed in proves nothing: a stranger signs up with a throwaway email and reads every user's row.
This is the leak most outside scans miss, because they only test as a logged-out visitor. Our lab tests as a signed-in stranger on every check.
In our lab study, 36 of 318 rebuilt apps (11%) leaked personal data only to signed-in users, on top of the 70 that leaked to anyone (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.
-- Looks restricted. It isn't, if anyone can sign up.
create policy "Authenticated users can view all profiles"
on public.profiles for select
to authenticated
using (true);
-- The same leak, written differently
create policy "Logged in users can view bookings"
on public.bookings for select
using (auth.uid() is not null);
Check it yourself
-- 1. Read policies that only check "is signed in"
select tablename, policyname, roles, qual
from pg_policies
where schemaname = 'public'
and cmd in ('SELECT', 'ALL')
and (qual = 'true' and roles && array['authenticated']::name[]
or qual ilike '%auth.uid() is not null%'
or qual ilike '%auth.role() = ''authenticated''%');
-- 2. Read a table as a brand-new user who owns nothing (nothing is changed)
begin;
set local role authenticated;
select set_config('request.jwt.claims',
'{"sub":"00000000-0000-0000-0000-000000000000","role":"authenticated"}', true);
select count(*) from public.profiles; -- more than 0 means any sign-up can read them
rollback;
The fix PatchLabs writes
PatchLabs scopes the read to the row's owner. When rows belong to a team or organisation, it scopes them to members instead, using the membership table your app already has.
drop policy "Authenticated users can view all profiles" on public.profiles;
create policy "Users can view own profile"
on public.profiles for select
to authenticated
using ((select auth.uid()) = id);
-- Shared rows: members of the team can read them, nobody else
create policy "Members read their team's projects"
on public.projects for select
to authenticated
using (team_id in (
select team_id from public.team_members
where user_id = (select auth.uid())
));
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
[BEFORE] anon → 0 rows DENIED stranger → 2 rows LEAK [FIX] scope reads to the owner [PROVE] anon → 0 rows DENIED stranger → 0 rows DENIED owner → 1 row, own OK [DONE] lab proof passed
The stranger is a second seeded user who owns none of the test rows. The fix passes only if that user reads nothing that isn't theirs and the owner still reads their own row.
When the pattern is intended
Reading every row is right when every signed-in user is supposed to see the whole table: a product catalogue, a public feed, or a member directory in a community where accounts are approved by hand.
The test is simple: who can create an account? If sign-up is open, treat to authenticated as public. When the answer depends on your business (should new sign-ups see the staff list before they are approved? should every buyer see every seller's email?), the report asks you that question in plain English, with a recommended answer.
Supabase linter
- 0024 permissive_rls_policy
- The closest lint, but it skips
SELECTpolicies by design, so it does not flag this.
Checked against the linter's source, Nov 2026. A static rule can't know whether your sign-up is open, so this needs a test as a real second user.
Related findings
Anonymous visitor can read personal data
Anyone with your app's public key can read emails, phone numbers or addresses.
PL-005Always-true policy
A policy that says yes to everyone. On writes, anyone can change or delete your data.
PL-004UPDATE policy missing WITH CHECK
Users may edit their own row, and nothing stops them changing its role, plan or owner.
Guides
- Firebase security rules basics: lock down Firestore and Storage
- What to do in the first hour after a leak
- Supabase RLS policies explained, with owner and stranger tests
- How to secure a Lovable app’s Supabase database
- Supabase Realtime security: keep private rows out of subscriptions
- Pre-launch security checklist for vibe-coded apps
Free check
Does your app have PL-002?
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.
Last reviewed 10 Nov 2026. Finding IDs are stable: link to them freely. Spotted a mistake? Tell us.