Skip to main content

Tenancy & Row-Level Security

See tenant-context and route-wrapping for the canonical, citable rule statements. This page covers the mechanics behind them.

Every tenant-scoped table enforces isolation at the Postgres layer:

ALTER TABLE identity.organisations ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON identity.organisations
USING (tenant_id = current_setting('app.tenant_id', true)::uuid);

Tenant scoping happens in two distinct steps:

  1. Membership validation at the route layer. Every catalog and directory route uses the per-app authedRoute / authedRouteWithParams helpers (which compose withAuth + withTenantAuth). withTenantAuth resolves the active tenant (x-tenant-id header → jwt.tenant_id fallback) and verifies the user has an active membership for it. No SET LOCAL happens here.
  2. DB session scoping at the tools layer. When a tool opens a transaction via withTenantContext() from @constellation-platform/db, the helper executes SELECT set_config('app.tenant_id', $1, true) (transaction-local) so RLS picks up the value for every subsequent query in the transaction. The tenant-scoped Prisma client wraps this for you when you use createTenantClient(...).

Routes that read but never call into a tool can still be RLS-safe by going through createTenantClient(...), which does the transaction + SET LOCAL for each scoped query.

Route wrapping

The wrap is mandatory: see route-wrapping for the rule, the canonical wrap style, the escape-hatch syntax, and the CI enforcement script.