Operator Modes and Break-Glass
What the static operator key may read under allow, audit and deny, the presets' defaults, the break-glass header, a tenant's own mode, and the access report that shows when it is safe to tighten.
The static operator key (CORTEX_API_KEY) is the deployment's own credential. Since 0.14.0 its
operator mode decides whether it can also read the memories it stores for others:
CORTEX_OPERATOR_DATA_READS is allow, audit or deny. The mode governs only requests the
operator key makes (and a deployment minter's mints); no other token is affected.
The three modes
| Operator-key action | allow | audit | deny |
|---|---|---|---|
| Operator routes: health, version, the console, minting tokens without data capabilities, the registry's reads, the access API and AuthZEN, erasures, audit, admin routes that return no content | served | served | served |
Reading stored memories (/v1/recall, /v1/answer, /v1/export, /v1/events, layer reads, a forget preview by query) | served | served, counted | needs the header |
Admin routes that return content (/v1/admin/events/{id}/chunks, …) | served | served, counted | needs the header |
| Reading a scope in a private region | served | served, counted | needs the header, and the region's people are told first |
In-band actions: registering scopes (a first write that registers one included), writing into a private region or a shared org-tree scope, minting a token that carries a data capability, a change-set activation that registers records or widens a private region, lowering a tenant's mode from deny | served | served, counted | needs the header |
| Other writes, imports, forgets and erasures | served | served | served |
"Counted" means the access report counts what deny would refuse (check operator_access), so
audit shows when an application has stopped depending on the key. "Needs the header" means
403 BREAK_GLASS_REQUIRED unless the request carries a break-glass header.
The presets' defaults
Unset, the mode is the deployment preset's (CORTEX_DEPLOYMENT_PRESET):
| Preset | Default operator mode |
|---|---|
cloud_shared_saas (with auth on) | deny |
on_prem_enterprise | allow |
cloud_private | allow |
dev_local (the binary's default) | allow |
| any preset with auth disabled | allow (audit and deny refuse to start) |
So a multi-tenant deployment keeps the operator key out of its tenants' memories unless an operator says why, and a single-organization deployment behaves as before. Set the variable to choose otherwise; any other value refuses the start (exit 78).
Break-glass
Under deny, an operator-key request that needs the header sends:
X-Cortex-Break-Glass: reason=support; ticket=SUP-1142reason is one of incident, legal, support, security, migration, restore or other;
ticket is your case reference (1 to 64 characters of [A-Za-z0-9._:-], no personal data); an optional
note (percent-encoded, at most 256 bytes) goes to the decision log only. Each such request is
audited with its reason and ticket. A read of a private region tells the region's people first (a
notice they read at GET /v1/access/notices), and is not served until that notice is stored. The
header is checked for its form, not its truth: it is an accountable override, not an authentication.
# A cloud_shared_saas server, the operator key, no header:
curl -sS -X POST "$CORTEX_URL/v1/recall" \
-H "Authorization: Bearer $CORTEX_API_KEY" -H "Content-Type: application/json" \
-d '{"scope": "org:acme/user:alice", "query": "standups"}'{"error_code": "BREAK_GLASS_REQUIRED",
"message": "the operator key's stored_rows action on /v1/recall needs X-Cortex-Break-Glass under the tenant's operator mode deny",
"details": {"class": "stored_rows", "mode": "deny", "route": "/v1/recall"}, "retriable": false}# The same request with the header is served:
curl -sS -X POST "$CORTEX_URL/v1/recall" \
-H "Authorization: Bearer $CORTEX_API_KEY" -H "Content-Type: application/json" \
-H "X-Cortex-Break-Glass: reason=support; ticket=SUP-1142" \
-d '{"scope": "org:acme/user:alice", "query": "standups"}'A malformed header is 400 INVALID_HEADER in every mode, naming what is wrong (an unknown reason, a
ticket outside its grammar, a repeated header). The embedded console asks the operator for a reason and
a ticket only after a request answers 403 BREAK_GLASS_REQUIRED.
A tenant's own mode
A tenant can be stricter, or, with a break-glass header, looser than the deployment:
curl -sS "$CORTEX_URL/v1/admin/access/tenant-settings?scope=org:acme" \
-H "Authorization: Bearer $CORTEX_API_KEY"
# {"scope":"org:acme","operator_data_reads":{"mode":"deny","source":"deployment"},"deployment_mode":"deny"}
curl -sS -X PUT "$CORTEX_URL/v1/admin/access/tenant-settings?scope=org:acme" \
-H "Authorization: Bearer $CORTEX_API_KEY" -H "Content-Type: application/json" \
-H "X-Cortex-Break-Glass: reason=migration; ticket=OPS-1142" \
-d '{"operator_data_reads": "audit"}'
# {"scope":"org:acme","operator_data_reads":{"mode":"audit","source":"tenant"},"deployment_mode":"deny",…}Lowering a tenant from deny is itself an in-band action and needs the header; raising it back needs
none.
Moving an application off the operator key
An application that reads, writes or mints with the operator key on a cloud_shared_saas deployment is
refused under deny. To move it without an outage:
- Set
CORTEX_OPERATOR_DATA_READS=audit(or the tenant's own mode toaudit). Everything is served as underallow, and each requestdenywould refuse is counted. - Move the application to per-person tokens (Identity and Tokens).
While it still uses the key,
X-Cortex-Subject-Token: <a person's token>on its requests evaluates each decision for that person too and counts the result (checksubject_shadow), without changing any answer: a dry run of the move. - Read the counts, then set
denyonceoperator_accessstays at zero:
curl -sS "$CORTEX_URL/v1/admin/access/report?scope=org:acme" -H "Authorization: Bearer $CORTEX_API_KEY"{"scope": "org:acme",
"checks": [{"check": "operator_access", "requests": 1, "outcomes": {"match": 0, "would_deny": 1},
"routes": [{"method": "POST", "route": "/v1/recall", "requests": 1, "would_deny": 1,
"reasons": {"stored_rows": 1}, "…": "…"}],
"classes": {"stored_rows": 1}}, "…"],
"tenant_settings": {"scope": "org:acme", "operator_data_reads": {"mode": "audit", "source": "tenant"}, "deployment_mode": "deny"}}The report's counts are in memory and restart with the process (counting_since). Every refused
request also writes one line under the log target cortex_api::decision (CORTEX_DECISION_LOG,
default denials), naming the link that failed.
See also
- Deployment Presets · Multi-Tenancy
- Upgrading to 0.14.0: the operator key on
cloud_shared_saas
Identity and Tokens
The principal chain, what a minted token may carry and how long it lives, tokens from your own identity provider, the tenant directory, and SCIM provisioning.
Applications and Sources
Register applications with a home per person and mounted sources, sync sources into containers that follow each source's own sharing, and see how agent writes carry what they read.