CortexDB Docs
Enterprise

Multi-Tenancy

How CortexDB isolates tenants — the tenant = token audience model, the cloud_shared_saas preset, per-tenant scopes, and how to provision multiple tenants.

CortexDB has two layers of isolation, and understanding the split is the key to a multi-tenant setup:

  1. Tenant — a hard boundary carried by the token's aud claim (e.g. cortexdb:tenant:acmetenant_id: "acme"). A request can never see or address another tenant's data.
  2. Scopes — the hierarchical addressing inside a tenant (org:acme/dept:eng/user:alice). This is how you separate users, teams, workspaces, and agents within one customer.

There is no tenant_id field in the request envelope — the tenant is derived entirely from the authenticated token. You isolate tenants by issuing each one tokens with a distinct aud.

1. Choose a deployment posture

The deployment preset sets the ceiling for every tenant:

PresetUse for
cloud_shared_saasMany isolated tenants sharing one deployment — the multi-tenant posture. Works self-hosted / on-prem too.
on_prem_enterpriseA single enterprise that owns the whole on-prem deployment — permissive (allow: ["*"], nothing denied).
cloud_privateA single tenant on dedicated cloud (global scopes become "their private pool").
dev_localLocal development only (unsigned actors allowed).

Set it with CORTEX_DEPLOYMENT_PRESET=cloud_shared_saas.

On-prem / self-hosted multi-tenancy: use cloud_shared_saas, NOT on_prem_enterprise

Multi-tenancy runs fully on your own hardware — the cloud_ prefix names the security posture (shared, tenants isolated), not the hosting. For multiple isolated tenants on-prem, set CORTEX_DEPLOYMENT_PRESET=cloud_shared_saas. Do not use on_prem_enterprise for that: it is allow: ["*"], deny: [] (verified live) — it does not deny cross-tenant reads/creates, so it provides no tenant isolation. on_prem_enterprise is for a single organization that owns the entire deployment.

What cloud_shared_saas allows and denies

The preset is the outermost tier of the four-tier stack (deployment → tenant → scope → actor) — a capability is allowed only if every tier allows it, and no inner tier can grant what the deployment denies.

Allowed (per tenant): create org / user / agent / ws scopes; scope.write; local / holistic / descend reads; understanding.*; forget.* including forget.gdpr; import.* / export.*; audit.read; blob.*; and policy.administer.{tenant,scope,actor} for tenant admins.

The isolation guarantees — what cloud_shared_saas DENIES

cloud_shared_saas denies, at the deployment tier (so no token can override):

  • scope.create.cross_tenant and scope.read.cross_tenant — no scope may cross a tenant boundary.
  • scope.create.global — the cross-tenant global:* pool is unavailable.
  • understanding.read.cross_scope and audit.read.cross_actor.

A denied request returns 403 POLICY_DENIED naming the capability. Isolation is reinforced cryptographically: the audit log hashes each row with a per-tenant pepper (body_hash = sha256(body ‖ tenant_pepper)), and API keys are scoped per-caller, so one tenant can never probe another's.

Defaults on this preset: actor.require_signed: true, scope.auto_register: true, audit.retention: P2Y, rate_limit.write: 100/s, rate_limit.read: 500/s.

2. Give each tenant its own token audience

This is the core of the setup. Every token carries an aud that names the tenant; validation rejects a token whose aud doesn't match the deployment's tenant binding. The token claims:

ClaimMeaning
issThe issuer — must be in the deployment's allowlist.
subThe actor, in type:id form (must equal the X-Cortex-Actor header).
audThe tenant binding, e.g. cortexdb:tenant:acme.
expExpiry, max 24h from issue.
caps / scopesOptional narrowing — a token can restrict capabilities (scopes like scope.write:org:acme/*) but never expand them.

There are two ways to mint per-tenant tokens:

Register your IdP's issuer and its public key (Ed25519 / RS256 / ES256) in the deployment's issuer allowlist. Your IdP then mints a token per session with the right aud (the customer's tenant), sub (the end-user or agent), a ≤24h exp, and any caps/scopes narrowing you want. CortexDB verifies the signature and enforces the tenant boundary — you own the tenant→user mapping.

Option B — The in-binary minter

POST /v1/auth/tokens mints tokens, but the minter is not configured by default — it returns 503 NOT_CONFIGURED ("token minting is not configured on this deployment") until you enable it with CORTEX_V1_MINTER_ENABLE=1. It also requires the auth.mint capability:

  • Self-hosted: the cloud_shared_saas and on_prem_enterprise presets grant auth.mint at the deployment tier (verified live), so once the minter is enabled you can issue child tokens.
  • Managed cloud: auth.mint is gated by plan tier (Starter+, scoped to your tenant); an operator token for provisioning multiple tenants comes from CortexDB (contact the team).

Provisioning many tenants needs operator/IdP-level control of aud

Within a single tenant, auth.mint lets you issue child tokens for your users. But minting tokens for different auds — i.e. standing up new tenants — is an operator/IdP concern: run an IdP whose token service sets aud per customer (Option A), or use an operator token on the managed cloud.

3. Lay out scopes inside each tenant

Everything for tenant acme lives under an org:acme/… tree. Scope types carry semantics:

TypeSemantics
orgTop-level tenant
dept / teamBusiness unit / cross-functional unit
wsWorkspace (collaborative)
appApplication instance
user / agent / serviceEnd-user / agent / service-account owner

A typical shape:

org:acme/dept:eng/user:alice          # an end-user's memory
org:acme/dept:eng/team:platform       # a shared team scope
org:acme/ws:project-x                 # a collaborative workspace
org:acme/agent:support-bot            # an agent's memory

scope.auto_register: true means writing to a new path provisions it — no pre-creation step. Reads use view: local (this scope only), holistic (this scope + ancestors), or descend (this scope + children), all bounded by the tenant. See Scopes.

4. Tenant administration

cloud_shared_saas grants policy.administer.{tenant,scope,actor}, so a tenant admin manages who can access which scope via scope-registry membership — PUT /v1/scopes/members?path=org:acme/dept:eng replaces the owner/writer/reader list for that subtree. The creating actor is auto-added as an owner, and delete is owner-gated. See Scopes API.

5. Verify a tenant's boundary

# Confirms the tenant the token resolved to
curl .../v1/auth/whoami -H "Authorization: Bearer <token>" -H "X-Cortex-Actor: user:alice"
# → { "caller": "user:alice", "tenant_id": "acme", "deployment_preset": "cloud_shared_saas", ... }

# The effective capability set for an actor+scope
curl ".../v1/policy/effective?actor=user:alice&scope=org:acme/user:alice" -H ...

See also

On this page