The Owner Sees Everything

A pooled multi-tenant app isolates tenants with row-level security.

CREATE TABLE invoice (
  id           uuid PRIMARY KEY,
  tenant_id    uuid NOT NULL,
  amount_cents bigint NOT NULL
);

ALTER TABLE invoice ENABLE ROW LEVEL SECURITY;

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

The application sets app.tenant_id correctly at the start of every request and connects with the same database role that ran the migration. Integration tests pass. In staging, one tenant's dashboard shows invoices from every tenant, and \d invoice confirms the policy exists and RLS is enabled. Why?

A

Row security is consulted only for queries that carry no other predicate; the application's own WHERE tenant_id = $1 filter takes precedence and disables the policy for that statement.

B

The migration role owns the table, and a table owner is not subject to row security unless the table also carries FORCE ROW LEVEL SECURITY. The policy is present and simply never applied to that connection.

C

current_setting returns NULL when the setting is missing, and tenant_id = NULL evaluates to unknown, which the row security check treats as a pass.

D

The policy declares no WITH CHECK clause, so PostgreSQL downgrades it to advisory and logs a warning at each use.