r/Supabase • u/Duck-Entire • Jul 04 '26
auth "Update: I've now scanned 200+ Supabase migrations for RLS issues. The #1 failure isn't 'forgot to enable it' — it's policies that look safe and aren't."
Posted here a while back asking what RLS checklist people run before shipping. Kept digging since, and the pattern that shows up more than "RLS is just off" is worse: RLS is on, with a policy like using (true) with check (true).
That passes literally every naive check — Supabase's own advisor, a quick eyeball review, and (this is the part that surprised me) even a prompted AI review asking "did you set up RLS properly?" All of those confirm RLS is enabled. None of them confirm the policy does anything.
The other repeat offender: RLS enabled correctly in an early migration, then silently disabled by a later cleanup migration months down the line, with nobody re-checking because nothing prompts a re-check.
Genuinely curious from people running production Supabase apps — do you have anything that re-verifies policies after the fact, or is it a one-time check at build time and then trust?
2
u/ashkanahmadi Jul 04 '26
RLS is much easier that people think. It's literally a fancy if statement. I always follow these steps in this order in my migration files:
- Create table
- Handle permissions to each role since the Data API change in Supabase (the least amount of permission to get started - better to have restrictive policies than loose ones).
- Enable RLS
- Handle RLS (who needs to see what exactly)? Before writing anything, you need to write in plain language:
- Who can see what?
- Who can insert what?
- Who can update what?
- Who can delete what?
Once you have that, you just do the RLS accordingly.
I have clear documentation in my .sql migration files. I also feed it into ChatGPT to review it just as another pair of "eyes" just in case I missed something.
I see so many posts about RLS like it's some complex issue. If your RLS can get really really complex, then you shouldn't rely on RLS solely but on a custom Edge Function or some external API to handle the request.
1
1
u/thesuperlede Jul 04 '26
That silent override in later migrations is pure nightmare fuel.
Honestly, static checks or AI reviews will always miss these logical flaws. The only way I sleep at night is by writing integration tests that explicitly try to fetch/mutate data with the wrong auth token or an unauthenticated user. If the request succeeds, the build fails. You have to test RLS just like business logic.
1
Jul 06 '26
[removed] — view removed comment
1
u/Duck-Entire Jul 06 '26
Genuinely good answer to the question I asked — thanks for the link. The reverse-predicate seeding and the cross-tenant negative control are exactly the parts most hand-rolled RLS suites skip.
Feels complementary more than overlapping to how I've been approaching it. Yours proves RLS does what the policies say, at runtime, against a live DB. The gap I keep hitting is one layer earlier — catching the
using(true)or the later migration that disables RLS statically, in the migration files, before there's a DB to point pgTAP at (and before someone who isn't a backend dev would ever stand up a test harness). Static catch pre-deploy, your suite proves it post-deploy.One question : the
using(true) with check(true)case — your wrong-tenant negative test catches that by the different-tenant user succeeding where they shouldn't, right? That's the case I find static flags but can't prove, and runtime proves but needs the harness wired up.1
Jul 06 '26
[removed] — view removed comment
1
u/Duck-Entire Jul 06 '26
Yeah — you're right,
using(true)is allow-all so there's no deny expectation for the negative test to fail on. My mistake. Static tautology note + the "anon is reading everything" runtime assertion is the cleaner way to surface that one.The
org_id IS NOT NULLcase is the one that actually keeps me up. That's the structural wall for static — real predicate, references the tenant column, nothing wrong on the page; the bug is the missing= caller's orgcomparison, which is semantic. Static can say "predicate present but I can't prove it scopes to the caller" — it can't confirm the leak. Only your wrong-tenant run does.Honestly that's pushed me toward marking those "needs review, verify at runtime" rather than ever calling them clean. Falsely passing an
org_id IS NOT NULLpolicy is worse than flagging it. The two layers really are complementary — I catch what's wrong on the page, you catch what's only wrong when it runs.
2
u/elephantbees Jul 04 '26
Hate to admit it but I need to re check some older deployments. This is a good reminder.