Security

Row-level security in Postgres: how we keep tenant data from ever mixing

· ~8 min read · Jamie, Founder, Blackglass

Multi-tenant SaaS lives or dies on one invariant: tenant A's rows never appear in tenant B's queries. Application-level filters are necessary but not sufficient — one missed WHERE workspace_id = ? in a new code path becomes a breach. Postgres row-level security (RLS) is our backstop: the database refuses illegal reads and writes even when application code regresses.

Greppable bypasses

Some jobs legitimately cross tenant boundaries — billing reconciliation, support with explicit customer consent, migration scripts. Those paths use a tiny audited helper and every callsite is tagged // RLS-BYPASS:<reason> so security review and CI grep stay honest. If you are evaluating us, ask for the bypass inventory; it should be boringly small.

What RLS does not fix

Compromised database superuser credentials bypass RLS by definition. Supply-chain attacks on dependencies bypass RLS. Human operators pasting production data into Slack bypass RLS. We still encrypt at rest, minimise retention, and run SAST in CI — RLS is one layer, not the whole story. The security overview walks the full stack.

Related