Tenancy Isolation
How every request is pinned to a tenant, how the token–tenant relationship is resolved, and how the cross-tenant boundary is enforced.
Ductor is multi-tenant from the ground up: every pool, rule, connection, workflow run, and API key belongs to a tenant. Isolation is the guarantee that one tenant can never read or mutate another's data — and it is enforced at the request boundary, not left to application code to remember.
Isolation vs entitlements
Isolation is whose data is this?; entitlements are how much may this tenant do?. Isolation is what makes an entitlement limit meaningful. This page covers the boundary; see also Concepts → Tenancy.
How the tenant is determined
Every authenticated request must resolve to exactly one tenant. The tenant interceptor resolves it from a priority cascade:
- JWT / Principal claims. The authenticated
Principal.TenantID(from the token'stenant_idclaim, or an API key's owning tenant) is authoritative and checked first. X-Tenant-IDheader. For direct Connect/HTTP requests, theX-Tenant-IDheader supplies the tenant when the credential is not itself tenant-scoped (for example a service token or an anonymous/dev deployment).- gRPC metadata. The
x-tenant-idmetadata equivalent, used on the gateway loopback path.
If none of these yields a tenant, the request is refused
(tenant ID required: set X-Tenant-ID header or authenticate with a tenant-scoped token). The resolved value is then format-validated (it must be
a well-formed tenant ID) and the tenant is state-checked — a tenant that does
not resolve to an active row is rejected before the handler runs.
A tenant-scoped token wins over the header
Because claims are consulted first, a caller holding a token whose tenant_id
claim is tenant A cannot escape into tenant B by adding an X-Tenant-ID: B
header — the claim already fixed the tenant. The header only supplies a tenant
when the credential does not carry one.
Exempt methods
A small, explicit set of methods runs without tenant enforcement — the health
probes (HealthService/Health, HealthService/Ready) and tenant bootstrap
(TenantService/CreateTenant, which by definition has no tenant yet). Everything
else requires a resolved, valid, active tenant.
The cross-tenant boundary
Resolving a tenant is not enough; the authorizer also verifies that the caller may act on the target tenant of the request:
- Self-match. For a normal request, the target tenant equals the principal's tenant, and the boundary check passes as a same-tenant self-match.
- Platform-admin crossing. A principal holding the deployment-issued
platform-adminrole may cross tenant boundaries through the governed control plane. Tenantadminremains confined to its own tenant. - Violation. Any other case where the principal's tenant does not match the target is a tenant boundary violation, logged and denied.
Control-plane RPCs carry a target tenant in the body
Some administrative RPCs act on a tenant named in the request body (not the caller's own). For those, the interceptor resolves the resource tenant from the declared body field (or a registered resolver) and runs the boundary check against that real target — never silently against the caller's own tenant.
Declared-but-unresolvable fails closed
If a method declares that it takes a target tenant from the request but the field is missing or empty, the request is denied rather than falling back to the principal's tenant. A control-plane method that cannot determine its target tenant must not degrade into a same-tenant self-match — that would be a cross-tenant escalation. Streaming handlers, which cannot peek the first message, resolve to unresolved for body-tenant methods and therefore fail closed.
Isolation extends to secrets
Tenant isolation is not only a request-time check — it is baked into how connector
credentials are stored. Each credential blob is AEAD-encrypted with tenant-bound
associated data (tenantID | providerKey), so a ciphertext decrypted under the
wrong tenant fails loudly. That blocks a cross-tenant credential swap even if a
blob were somehow moved between tenants. See
Hardening → Connector credential encryption.
Isolation guarantees, summarized
- Every authenticated request is pinned to exactly one validated, active tenant, or it is refused.
- A tenant-scoped token's claim is authoritative and cannot be overridden by a header.
- Cross-tenant access requires the deployment-issued
platform-adminrole; tenantadminand all other mismatches are denied. - Control-plane RPCs check against the real target tenant and fail closed when it cannot be resolved.
- Credential secrets are cryptographically bound to their tenant at rest.
Clearing Entitlements
The capability plane — what it gates, its report / strict / off enforcement postures, unprovisioned grace, and tenant-scoped evaluation.
Key custody & crypto-shred (BYOK)
Per-tenant key-encryption-key custody — platform-managed vs BYOK modes, the binding lifecycle, the staged crypto-shred state machine, and pluggable KMS backends.