DEV Community

Discussion on: 7 security bugs AI agents write in Next.js + Supabase + Stripe apps (and the rules that stop them)

Collapse
 
cekuu35 profile image
Cenk KURTOĞLU •

The select tablename, policyname from pg_policies where qual = 'true' query is the right first sweep, but the bug class just past it is the one that ships. Agents rarely leave a bare USING (true) once they've been told not to - they write a policy that mentions the user but doesn't scope to the row:

-- passes both "RLS enabled" and "there's a policy, it mentions auth"
create policy "Users can view profiles"
  on public.profiles for select
  using (auth.uid() is not null);
Enter fullscreen mode Exit fullscreen mode

qual = 'true' misses this (the expression isn't literally true), a linter is satisfied, and every signed-in user reads every row - emails, stripe_customer_id, the lot. Catching it needs the roles, not just the qual:

select tablename, policyname, cmd, qual, with_check
from pg_policies
where schemaname = 'public'
  and (qual is null or qual !~ ('uid\(\)\s*=\s*|=\s*.*uid\(\)'));
Enter fullscreen mode Exit fullscreen mode

Empty result is the baseline you want. I hit exactly this shape in three live apps last week (the "does this email exist" login helper is the recurring offender). Put the same red/green test next to your migration so CI catches it: a runnable minimal demo - npm ci && npm test, red on broken, green on fixed, no Docker - is here: github.com/cekuu35/supabase-rls-le...

Collapse
 
edtrackai profile image
Stack & Ship •

Good catch, and thanks for the concrete example. auth.uid() is not null is exactly the policy that satisfies a checklist while scoping nothing, and a qual = 'true' sweep can't see it. Your regex check is a handy second pass; I'd add a quick two-user test as well, where user A tries to select user B's row, because that catches shapes a pattern match won't, such as a join through a membership table that isn't filtered.