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_idagainst 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.
Related pages
- Organizations and tenancy — the org model from a user’s perspective.
- Security model — where tenant isolation sits in the overall model.
- Audit logs — audit search is scoped by the same RLS.