Skip to content
VaultTerm
Browse docs

security-architecture

Tenant isolation

How VaultTerm isolates tenants with Postgres row-level security — org_id scoping from the authenticated context, FORCE ROW LEVEL SECURITY, and runWithOrg for API-token routes so RLS fails closed.

Updated Jun 23, 2026

VaultTerm is multi-tenant, and it isolates tenants at the layer where mistakes are least forgivable: the database. Isolation is enforced by Postgres row-level security (RLS), not by hoping every query in the application remembers to filter by tenant. If the tenant context is missing, queries return nothing rather than leaking across tenants.

Row-level security by org_id

Every tenant table carries an org_id column and an RLS policy that admits a row only when its org_id matches the current organization in context. That context is supplied as a session setting — a Postgres GUC set from the authenticated request’s tenant, per connection:

  • The authenticated context resolves which org the caller belongs to.
  • That org id is set as the current-org GUC on the database session.
  • The RLS policy compares each row’s org_id against that setting.

If the setting is unset, it resolves to no organization, and the policy matches zero rows — the isolation fails closed. A query that forgets to establish tenant context returns nothing; it never returns another tenant’s data.

Forced even for the owner

The tenant tables use FORCE ROW LEVEL SECURITY, so the policy applies even to the table owner — the role the application connects as. There is no privileged connection that bypasses RLS in normal operation. The isolation is a property of the database, independent of how careful any individual query is.

API-token routes establish context the same way

Not every request comes from an interactive, session-authenticated user. API-token routes — such as SCIM provisioning and the Events API — authenticate with a token rather than a user session. These routes establish the tenant context the same way, by setting the org context before running their work, using the shared runWithOrg helper.

This matters because it keeps RLS failing closed for token traffic too. By wrapping their work in runWithOrg, token routes ensure the current-org GUC is set for the duration of their database work; if it were not, RLS would simply return no rows. The same mechanism that protects interactive requests protects machine-to-machine requests, with no separate, weaker path.

A note on the Default org

Historically, VaultTerm placed individual sign-ups into a shared Default organization before per-account org isolation was completed. The RLS model above is what scopes data even within that shared arrangement, and per-account isolation is the direction the tenancy model has moved. See Organizations and tenancy for how organizations work today.