Operations, Security & Reliability
Row-Level Security and Data Boundaries
Row-level security adds a database-enforced predicate to ordinary table access, but safe tenant isolation also requires trustworthy identity propagation, constrained ownership and bypass roles, complete policy coverage, and adversarial tests.
- RLS is defense in depth, not tenant identity discovery.
- Visibility and writes need separate reasoning.
- Owners and privileged roles may bypass policy.
- Policy composition changes meaning.
- RLS does not solve every side channel.
- A denied-case suite is mandatory.
-- This multi-tenant example extends the canonical
-- commerce.orders(order_id, customer_id, placed_at, status) with tenant_id.
ALTER TABLE commerce.orders ENABLE ROW LEVEL SECURITY;
ALTER TABLE commerce.orders FORCE ROW LEVEL SECURITY;
CREATE POLICY orders_tenant_access ON commerce.orders
AS PERMISSIVE FOR ALL TO orders_runtime
USING (tenant_id = current_setting('app.tenant_id', true)::uuid)
WITH CHECK (tenant_id = current_setting('app.tenant_id', true)::uuid);
CREATE POLICY orders_not_archived ON commerce.orders
AS RESTRICTIVE FOR SELECT TO orders_runtime
USING (status <> 'ARCHIVED');| Case | Expected |
|---|---|
| Tenant A selects A order | row returned |
| Tenant A selects/updates/deletes B order | zero rows or denied |
| Tenant A inserts/retags row as B | WITH CHECK rejects |
| Identity absent or malformed | fail closed; transaction discarded/reset |
| Table owner or BYPASSRLS role | explicitly demonstrates bypass |
| Pooled connection reused for B | transaction-local identity reset; only B visible |
Set tenant context from authenticated server-side data inside the transaction, not from raw request text. Prefer a transaction-local setting and revoke direct table access from roles that should enter through a trusted routine or view. The exact mechanism is vendor-specific; the invariant is a non-forgeable identity-to-session binding.