Multi-tenant RLS for compliance
Multi-tenant RLS for compliance is enforcing tenant isolation inside Postgres itself (a row-level-security policy the database applies to every query) so a forgotten WHERE clause can't leak another tenant's data. Caisson's @caisson/tenancy-rls makes it fail-closed: FORCE row-level security plus a withTenant wrapper mean an unscoped query returns zero rows, and the isolation ships as a test.
In code
// The sole entry point for a tenant query. Skip it and the policy predicate sees no bound
// tenant -> zero rows returned, never another tenant's data (fail-closed by construction).
export async function withTenant<T>(
db: Transactor,
accountId: string,
fn: (tx: TenantExecutor) => Promise<T>,
): Promise<T> {
if (accountId.length === 0) {
throw new TenancyError("Refusing to run a tenant query without an account id");
}
return db.transaction(async (tx) => {
await tx.query(`SELECT set_config($1, $2, true)`, [TENANT_GUC, accountId]);
await ensureRoleGuard(db, tx, "app"); // refuses a SUPERUSER / BYPASSRLS role
await tx.exec(`SET LOCAL ROLE app`);
return fn(tx);
});
}How it holds
FORCE RLS, not just ENABLE
Plain ENABLE ROW LEVEL SECURITY still lets the table owner bypass the policy. buildTenantPolicySql always emits FORCE ROW LEVEL SECURITY too, so the isolation applies even to the owning connection, only a genuine superuser or BYPASSRLS role escapes it.
withTenant is the sole entry point
withTenant binds the tenant GUC, verifies then drops to the non-superuser app role, and runs your callback. A code path that forgets it never sets the GUC, so the policy predicate compares against null and the query returns nothing, a leak becomes zero rows, not another tenant's data.
The role is verified, not assumed
ensureRoleGuard queries pg_roles once per connection and throws before SET ROLE if the app role turns out to be SUPERUSER or BYPASSRLS, a misconfigured role can't silently reopen cross-tenant access with no runtime signal.
The control is also the evidence
A cross-tenant read returning zero rows is an assertion in the test suite that runs every build, and the RLS-force evidence collector turns that live check into a pass/flag input for the SOC 2 CC6.x / HIPAA 164.312(a) evidence pack, the isolation proves itself.