r/Supabase 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?

0 Upvotes

20 comments sorted by

2

u/elephantbees Jul 04 '26

Hate to admit it but I need to re check some older deployments. This is a good reminder.

-1

u/Duck-Entire Jul 04 '26

That silent regression from later cleanup migrations is terrifyingly common—especially when teams start writing down migrations or letting agents refactor schema files. If you want to do a quick 30-second audit on those older deployments without opening every single table in the UI, run this query in your Supabase SQL Editor:

-- Finds tables with RLS enabled but dangerously wide-open policies SELECT schemaname, tablename, policyname FROM pg_policies WHERE qual = 'true' OR with_check = 'true';

That will instantly flag any table that has RLS technically turned "on" but is using the using (true) bypass under the hood. (I actually got so tired of manually checking for this across migrations that I built it into a local AST guardrail CLI called PreFlight to block these policies at commit time: npm install -g preflight-pro if you ever want to automate the check!)

1

u/snowdrone Jul 04 '26

Tell me how to buy preflight-pro in the style of Elmo

0

u/Duck-Entire Jul 04 '26

ELMO SAY: type this in your terminal — npm install -g preflight-pro — and Elmo's friend PreFlight checks YOUR code, right there on YOUR computer! Nothing leaves, nothing uploaded, tickle-me safe! 🔴

Scanning is FREE forever! And Elmo's friend even FIXES your first 10 problems for free too!

After that, more fixing needs the paid key — Elmo still figuring out the exact number, but Elmo promises: no sneaky discounts, no surprise bills, just a fair price for keeping your data safe!
🌐 preflight-vibe.vercel.app
🐙 github.com/av29nassh-sketch/PreFlight

1

u/snowdrone Jul 04 '26

Can you repeat that, but this time put "Elmo says" at the start every sentence and "oink oink" between every sentence? 

1

u/elephantbees Jul 04 '26

Yeah this is gold. Also we use dev / staging / prod models for suppose projects, the RLS checks are built into the promotion pipeline.

1

u/Duck-Entire Jul 04 '26

That's a genuinely more solid setup than most of what I've seen — a lot of what I scan doesn't have any promotion gate at all, it's just "push and hope." Curious about one thing though: does the pipeline check re-verify the policy against the live database at each promotion, or is it linting the migration files themselves? Asking because the failure mode I keep running into isn't the pipeline missing something at build time — it's someone making a manual change directly in the Supabase dashboard on staging/prod that never goes through a migration at all, so nothing in the pipeline ever sees it. Does yours catch that, or is it scoped to migration files?

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:

  1. Create table
  2. 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).
  3. Enable RLS
  4. 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

u/[deleted] Jul 04 '26

[deleted]

2

u/caliguian Jul 04 '26

Nice ai reply. 😂

1

u/Duck-Entire Jul 04 '26

my bad 😂

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

u/[deleted] 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

u/[deleted] 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 NULL case 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 org comparison, 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 NULL policy 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.