Vibin R.

ALL POSTS

Row-level security without losing your mind

Postgres can enforce multi-tenancy for you. The catch is every connection has to tell the truth about who it is.

30 Jul 2026 · 6 min readPostgresSecurityBackend

Every multi-tenant app starts with the same quiet agreement: every query will remember to filter by tenant. It holds until the day someone writes a report query at 6pm on a Friday and forgets. Row-level security moves that promise from your discipline into the database.

The whole idea in one policy

sql

ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON invoices
USING (tenant_id = current_setting('app.tenant_id')::uuid);

From that moment a bare SELECT * FROM invoices returns only the current tenant's rows. Not because the query said so, but because the table refuses to hand over anything else.

The part that bites

  • The setting has to be set per connection, on every checkout from the pool, or the query errors instead of leaking — which is the failure mode you want.
  • The table owner bypasses RLS by default. Your migration user is not the user your app should connect as.
  • Policies apply per statement, so a join across two protected tables is filtered twice. That is correct, and occasionally surprising.
Security you have to remember is a bug waiting for a deadline.