It Works Until It Pools

Row-level security is enforced against a session setting the app writes with SET app.tenant_id at the start of each request. It passes every test, then in production behind a transaction-pooling connection pooler users intermittently see another tenant's rows. What went wrong?

A

The policy expression is frozen into the cached query plan, so the first tenant's id is reused for every later execution of that prepared statement on the connection

B

Session settings persist on the backend connection, which the pooler hands to the next tenant's transaction, so the policy evaluates against a stale tenant id

C

The pooler strips SET statements from the wire protocol, so the policy always evaluates against an empty tenant id and its predicate matches every row

D

Row-level security is only enforced on SELECT, so rows belonging to other tenants surface as soon as a request performs any write inside the same transaction