How we use Postgres RLS for true multi-tenant isolation
Every table, every query, every test. The pattern, the helper functions, and the audit script.
Multi-tenancy without RLS is multi-tenancy with bugs. We enable Row Level Security on every table, scope every policy to a helper function, and run a cross-tenant audit script before every deploy.
The helper function pattern
get_my_tenant_id() returns the current user’s tenant_id by reading the tenant_users table. SECURITY DEFINER + STABLE means it’s safe and fast. Every policy reads it: tenant_id = get_my_tenant_id() OR is_platform_admin().
The audit script
Every CI run executes scripts/rls-audit.ts which logs in as user A and tries to read user B’s data across every table. If any row leaks, the build fails.
What it doesn’t protect against
Bugs in the application layer that bypass RLS by using the service-role key. Our rule: service-role client is server-only, never imported from a Client Component, and every server-side use is reviewed.