Skip to main content

Tenant isolation

A namespace is a physical store. By default every capability that is scoped to a namespace shares it, so if two tenants both hold tokens for kv/scratchpad, they read and write the same data. That's fine for a single-tenant deployment, but in a multi-tenant one it's a data-leak.

Tenant isolation scopes each request's storage to the caller's capability tenant, so two tenants sharing a namespace name get physically separate data.

# config.yaml
tenant_isolation: true

Behavior​

  • Off (default). Storage is byte-for-byte identical to how it worked before isolation existed. A single-tenant deployment is unaffected and existing data keeps working. This is why it's opt-in.
  • On. Every block's storage key is prefixed with the caller's tenant (from the capability token). Enforcement lives in one place (the service layer, before the driver is ever touched), so a driver can't leak across tenants even by accident. All tokens must carry a tenant.
tenant "acme" ─┐
├─ kv.put("scratchpad", "k", …) → stored under acme
tenant "beta" ─┘ → stored under beta (separate)

Tenant A cannot read, scan, overwrite, or delete tenant B's records in the same namespace, and vice-versa.

Coverage​

Isolation covers all six blocks: kv, episodic, lease, graph, artifact, and semantic. The first five qualify the storage namespace string; the semantic block, which is bound per-namespace, carries a tenant column so (tenant, id) is a record's identity and every search/delete/expire is tenant-scoped. Two tenants can reuse the same semantic record id without colliding.

Enabling it on an existing deployment​

Turning isolation on changes where data is addressed. Data written before enabling it lives under the un-prefixed namespace and won't be visible to tenant-scoped reads afterward, which is the point (it was previously shared). Enable it from the start of a multi-tenant deployment, or migrate deliberately.

The tenant comes from the capability token, so issue every token with one:

mindctl token issue --tenant acme --agent a1 --ns 'kv/*' --ops '*'

See Capabilities for how tokens carry the tenant.