CortexDB Docs
Core Concepts

Authorization

PASETO tokens, the four-tier capability stack, and how CortexDB evaluates authorization.

CortexDB authorizes every request through a four-tier capability stack and identifies callers with PASETO v4 public tokens. Authorization is enforced at the storage layer, not in application code.

The four-tier stack

Decisions cascade from outer to inner. An allow at an inner tier can override an outer allow (be more specific), but an outer deny is final.

1. Deployment policy   ← preset-defined floor (cannot be overridden by tenant/scope)
2. Tenant policy       ← per-tenant defaults
3. Scope policy        ← per-scope ACLs and members
4. Actor policy        ← per-actor overrides

Deployment presets

Set with CORTEX_DEPLOYMENT_PRESET. These four values are the complete set.

PresetNotes
dev_localDefault. Experimentals enabled. Local development only. (Before v0.9.10 it also accepted unsigned actor-only requests; since v0.9.10 a keyed server rejects them with 401.)
on_prem_enterpriseMost permissive production preset; cross-workspace GDPR erasure allowed by default.
cloud_shared_saasShared-cloud preset; experimentals gated; cross-workspace GDPR denied by default.
cloud_privateSingle-tenant cloud preset; experimentals gated.

Authenticating an agent

Every request in a secured deployment carries:

Authorization: Bearer <PASETO v4 public token>
X-Cortex-Actor: user:alice

Self-hosted auth modes

With CORTEX_API_KEY set, the key value itself is a working Bearer credential (constant-time compared), alongside PASETO/JWT bearers. To run unauthenticated for local dev, start with CORTEX_INSECURE_NO_AUTH=1 — requests are served without a token (served as user:local, or as whatever X-Cortex-Actor names; it does not restrict the bind, so publish the port on loopback only, -p 127.0.0.1:3141:3141). With neither on a network-exposed bind, the server auto-generates a key and requires it. There is no /v1/auth/login.

On a network, run v0.9.10 or later

Run v0.9.10 or later on any network-reachable server: from v0.9.10 every preset requires the key, and a request carrying only an X-Cortex-Actor header gets 401 MISSING_TOKEN. If you must run an older image on a network, set CORTEX_DEPLOYMENT_PRESET=on_prem_enterprise (or a cloud_* preset).

Pick your auth path

You want to…UseGet a token by
Try the API in 60 s (prototype, AI self-install)Anonymous sign-upPOST /v1/auth/signup {} → token + actor + scope. Self-hosted, the in-binary minter is off by default on a keyed server (503 NOT_CONFIGURED) — enable with CORTEX_V1_MINTER_ENABLE=1; under CORTEX_INSECURE_NO_AUTH=1 signup is served. Standard on the managed cloud.
Run a service accountServer-side mintToken with auth.mint, then POST /v1/auth/tokens with subject/ttl/scopes. In-binary minter is dev-only.
Production with permanent identitiesYour IdP issues PASETORegister your IdP's signing key (IssuerRegistry); CortexDB verifies.
Dashboard / MCP servercx_live_… API key from the dashboardA separate credential format the BFF exchanges for PASETO server-side.

Token claims

{
  "iss":  "https://idp.acme.com/realms/main",
  "sub":  "user:alice",
  "aud":  "cortexdb:tenant:acme",
  "exp":  1763212929,
  "jti":  "j_01HX...",
  "deployment": "cloud_shared_saas",
  "caps":   ["scope.read.local", "scope.write"],
  "scopes": ["scope.read:org:acme/dept:eng/*", "scope.write:org:acme/dept:eng/user:alice"]
}

X-Cortex-Actor must match sub. A token can only narrow what policy already allows — it cannot grant a capability the deployment denies.

  • caps narrows the token to the listed capabilities (empty inherits the deployment and tenant defaults).
  • scopes confines capabilities to scope subtrees. Each entry is <capability>:<scope-path>[/*]: an exact capability or a prefix (scope.read covers scope.read.local, scope.read.holistic, …), then the subtree it is confined to. A capability no entry names stays unconfined. A bare path such as "org:acme/dept:eng" is refused at mint with 422 INVALID_BODY ("expected <capability>:<scope-path>[/*]").
  • Neither claim makes the caller a member of a scope. Reading or writing a registered scope also needs a seat on its scope roster; a freshly minted agent that holds the right capabilities but no seat is refused with 403 POLICY_DENIED.

Denials

A 403 cites the tier + capability + reason:

{
  "error_code": "POLICY_DENIED",
  "message":    "forget.gdpr.cross_workspace denied",
  "details": {
    "capability":      "forget.gdpr.cross_workspace",
    "decided_by_tier": "deployment",
    "reason":          "preset cloud_shared_saas denies"
  },
  "retriable": false
}

Introspect the allow/deny surface with GET /v1/policy/effective?actor=…&scope=…. Note: in v0.9.9, v0.9.13 and v0.10.1 that endpoint returns allowed/denied as flat capability-name arrays (plus deployment_preset, rate_limits) — the per-entry tier/reason shown in the denial body is not (yet) returned there. See the Policy API reference.

Common capabilities

CapabilityGates
scope.read.local / .holistic / .descendread at scope / traverse ancestors / traverse descendants
scope.write / .elevated / .on_behalf_of / .about_otherwrite / write above natural scope / set observed_actor / set subject
forget.cascade.derived_only / .redact_eventsselective forget / blank event payloads
forget.gdpr / .cross_workspacetrue event deletion via /v1/erasures / propagate into co-owned workspaces
llm.invokecall /v1/answer
diagnostics.readrequest diagnostics ≠ "none"
understanding.synthesizetrigger /v1/understanding/synthesize

FAQ

Can an inner policy override an outer one? An inner allow can be more specific; an outer deny is always final.

Does CortexDB use flat tenant identifiers? No — hierarchical scopes, so policy inherits down the hierarchy.

See also

On this page