← All transmissions

7 min

Put the rule in the database, then prove it holds

Row-level security is the right place for authorisation. It is also the easiest thing in your stack to get subtly, silently wrong — and Supabase finally shipped a way to check.

I write permission rules into Postgres row-level security rather than into application code, and I will argue for that in any room. A rule in the app is a rule the app agrees to follow. A rule in the database is a rule nothing can talk its way past: not the web console, not the mobile client, not the API, not a script someone runs at midnight against production because they were in a hurry.

The catch is that RLS fails quietly. A policy that is too permissive does not throw. It returns rows. Everything looks like it works, because from the inside, over-permission and correctness are indistinguishable — you asked for data and you got data.

The failure mode nobody sees

On a wedding platform I built, a groom-side editor must not be able to read a bride-side guest. On a school system, a signed-in student sees their own attendance and grades and nothing else, and the same rule has to hold across a web console, a React Native app and the REST API simultaneously. That is exactly why the rule belongs in the database: three surfaces, one law.

But nothing in a normal development loop tells you the policy is right. You sign in as yourself, you see your own data, and the page looks correct. The bug is the row you should not have been able to reach and never thought to ask for.

Testing it by hand

The technique that has always worked is impersonation inside a transaction: become the role, set the claims the request would carry, run the query, assert what comes back. Wrap it in a transaction you roll back and it costs you nothing.

Assert the negative, not the positive
begin;

-- become a groom-side editor
set local role authenticated;
set local request.jwt.claims = '{"sub": "<editor-uuid>", "role": "authenticated"}';

-- the rows they are allowed to see
select count(*) from guests where side = 'groom';   -- expect > 0

-- the rows they must never see
select count(*) from guests where side = 'bride';   -- expect 0

rollback;

The second query is the whole test. Checking that a user can see their own data proves almost nothing; every broken policy passes that check. The assertion with value is the one that expects zero, because that is the one that fails loudly when a policy drifts.

What changed in 2026

Supabase shipped an RLS Tester in preview in April 2026, which drags this out of the ad-hoc-SQL-in-a-scratch-file category and into something you can actually run against a policy while you write it. That matters less for the mechanics — the SQL above always worked — and more for the habit. A test you have to hand-roll is a test most teams skip.

A few other 2026 changes are worth knowing if you are running on this stack:

  • The client libraries now emit W3C trace context, so an OpenTelemetry, Sentry, Datadog or Honeycomb pipeline can follow a request end to end instead of losing it at the database boundary.
  • PostgREST retries automatically across all four official clients, which quietly removes a class of flaky-network bug from mobile apps.
  • pg-delta brought declarative schemas, so migrations start looking like a described end state rather than an accumulated pile of diffs.
  • Secret keys got GitHub Push Protection, blocking a committed key before it lands rather than after you rotate it in a panic.
  • Passkeys landed in beta for Auth in July, which is the first genuinely good answer to the shared-password problem in small-team apps.
  • Branching without Git became the default in May, and the platform picked up ISO/IEC 27001:2022 certification.

The rule I actually follow

Write the policy, then write the test that proves the wrong user gets nothing. If a permission rule has never been observed refusing someone, it has not been tested — it has been assumed, and an assumption in the authorisation layer is just a vulnerability with good manners.

If the frontend is the only thing stopping you, nothing is stopping you.

Sources