Security & Compliance
Encryption, TLS / mTLS, RBAC, rate limits, breach detection, audit, SIEM, data residency, DSAR — every governance knob in cortex.toml.
This page covers [security], [governance], and [compliance] in cortex.toml — about 80 fields.
None are on by default: the out-of-the-box config is permissive so you can evaluate quickly. Before
production, walk this page top to bottom and turn things on.
Two controls are stored but NOT enforced in the current release
Retention TTLs and rate-limit quotas are stored/echoed but not enforced — memories don't
auto-expire and over-limit requests aren't rejected. Drive deletion from your own schedule via
/v1/forget / /v1/erasures, and enforce hard rate limits at your LB. Both are called out below.
Governance — the foundation
[governance]
default_retention_ttl_secs = 2592000 # 30 days; null = forever
max_retention_secs = 220752000 # 7 years (default cap)
pii_detection = true # default
pii_handling = "Flag" # "Flag" (default) | "Block"
audit_logging = true # default| Field | Default | What it does |
|---|---|---|
default_retention_ttl_secs | null (forever) | TTL applied to events with no explicit retention. |
max_retention_secs | 7 years | Hard upper bound, even for explicit "forever". |
pii_detection | true | Scan every event for PII before commit. |
pii_handling | "Flag" | "Flag": accept + mark for re-scan. "Block": reject if scanner is down or finds PII. |
audit_logging | true | Log every read/write to the audit trail (queryable via /v1/audit). |
Retention is not auto-enforced; the audit path is /v1/audit
TTL / expiration values (including per-event ttl_seconds / expiration_date) are stored but not
enforced — memories don't auto-expire (the server logs a warning when a TTL is set). Deletion is an
explicit operation: use /v1/forget or
/v1/erasures on your own schedule. The audit trail is queried at
/v1/audit (not /v1/admin/audit, which is 404) — see Audit.
Encryption at rest
[security.encryption]
enabled = false # default off — turn this on
key_file = "/etc/cortexdb/keys/master.key"
key_rotation_interval_secs = 7776000 # 90 days
blob_store_sse_kms = false # use cloud KMS for blobsWhen enabled = true, all RocksDB column families and the WAL are encrypted with AES-256-GCM. The
key_file (32 random bytes) is required, must be mode 0600 owned by the cortexdb user, and old keys
are retained for decrypting historical segments across rotations.
head -c 32 /dev/urandom | base64 > /etc/cortexdb/keys/master.key
chmod 600 /etc/cortexdb/keys/master.key
chown cortexdb:cortexdb /etc/cortexdb/keys/master.keyFor HSM / cloud-KMS keys, mount the unwrapped key into tmpfs, point key_file at it, and rotate via
your KMS policy.
TLS and mTLS
[security.tls]
api_tls_enabled = false # default — terminate at LB instead
cert_path = "/etc/cortexdb/tls/cert.pem"
key_path = "/etc/cortexdb/tls/key.pem"
ca_cert_path = "/etc/cortexdb/tls/ca.pem"
mtls_enabled = false # require client certs on internal RPC
min_tls_version = "1.2" # "1.2" | "1.3"For "TLS at the LB, plaintext inside VPC", leave everything off. For zero-trust, enable API TLS + mTLS
and set min_tls_version = "1.3" — every client (SDK, dashboard, peer node) needs a cert signed by the
CA at ca_cert_path.
RBAC and authentication
[security.rbac]
enabled = false # default off — anonymous access
default_role = "reader" # role for unauthenticated requests
oidc_issuer = "" # OIDC discovery URL
oidc_audience = "cortexdb" # JWT aud claim
role_mappings = [] # OIDC group → role mappings
require_mfa = falseWhen enabled = true, every request must carry a valid OIDC bearer token; claims map to a role via
role_mappings, and the role's capabilities determine access.
[security.rbac]
enabled = true
oidc_issuer = "https://login.acme.com/realms/main"
oidc_audience = "cortexdb"
[[security.rbac.role_mappings]]
oidc_group = "cortexdb-admins"
role = "admin"
[[security.rbac.role_mappings]]
oidc_group = "engineers"
role = "writer"The role catalog (admin/writer/reader) ships with the binary — see
Authorization. Custom roles go under [[security.rbac.custom_role]].
Simple bearer auth without OIDC
Setting CORTEX_API_KEY requires Authorization: Bearer on every request, and the value itself is the
working credential (constant-time compared; PASETO v4 / JWT bearers also work). There is no shipped
default key. To run unauthenticated for local dev, set CORTEX_INSECURE_NO_AUTH=1 (every request
served as user:local; binds loopback). A keyless, network-exposed server auto-generates a key and
requires it rather than serving open.
Rate limiting
[security.rate_limit]
enabled = false # default — no rate limit
default_rpm = 600 # requests per minute per actor
default_rpd = 50000 # requests per day per actor
burst = 20 # short-burst capacityRate-limit quotas are not enforced
The server emits informational rate-limit headers but does not reject over-limit requests —
enforcement is planned. Enforce hard limits at your LB / gateway. The header value carries the
deployment's configured limit with a unit (e.g. x-ratelimit-limit: 50000/s), plus
x-ratelimit-reset:
x-ratelimit-limit: 50000/s
x-ratelimit-remaining: 587
x-ratelimit-reset: 2026-05-18T15:42:00ZBreach detection
[security.breach_detection]
enabled = false # default off
max_failed_auth = 5
auth_window_secs = 300 # 5-min sliding window
lockout_duration_secs = 3600 # 1-hour lockout
webhook_url = "" # POST URL for alertsFailed auths (bad JWT, expired token, denied capability) are counted per actor; exceeding
max_failed_auth within auth_window_secs triggers a lockout and an alert.
Field-level access control
[security.field_access]
enabled = false # default off
[security.field_access.masked_fields]
reader = ["content.text", "subject"] # fields masked for this roleWhen enabled, response payloads have masked fields replaced with "[REDACTED]" based on the actor's
role — useful for shipping recall results into a UI without exposing raw memory text.
Data residency
[compliance.data_residency]
enabled = false # default off
default_region = "us-east-1"
allowed_regions = [] # empty = allow all
require_residency_tag = falseWhen enabled, writes to a region not in allowed_regions are rejected with 403 residency_violation.
Region-aware partitioning is part of the experimental cluster design and not operational — today
residency applies within a single-node deployment, so run one deployment per region for EU-only /
US-only data planes.
Consent and purpose
[compliance.consent]
require_consent = false # default — no consent enforcement
default_purposes = ["customer_support"]
max_validity_secs = 31536000 # 1 year defaultWhen require_consent = true, every write must reference a consent record, and reads filter to events
whose consent is still valid and whose purposes include the caller's.
POST /v1/consent is not shipped
The consent schema scaffolding exists, but the POST /v1/consent endpoint returns 404 on v0.9.9
— consent-record minting/enforcement is planned, not available. Don't build a lawful-basis flow on
it yet.
Classification and auto-redaction
[compliance.classification]
auto_classify = false # LLM assigns sensitivity tags on write
default_sensitivity = "internal" # "public" | "internal" | "confidential" | "restricted"
auto_redact_above = "confidential" # auto-redact fields above this levelWith auto_classify = true, the extraction LLM also assigns a sensitivity tag; fields above
auto_redact_above are stored encrypted and decrypted only for authorized roles.
DSAR (Data Subject Access Requests)
[compliance.dsar]
enabled = false # default offWhen enabled, /v1/erasures accepts subject-targeted delete requests:
# 1. Preview
curl -X POST https://api/v1/erasures/preview \
-H "Authorization: Bearer $TOKEN" \
-d '{"scope": "org:acme/user:alice", "audit_note": "DSR-1234 preview"}'
# 2. Execute — confirm_all is REQUIRED with no selector.memory_ids
curl -X POST https://api/v1/erasures \
-H "Authorization: Bearer $TOKEN" \
-d '{
"scope": "org:acme/user:alice",
"from_preview_id": "ervw_01HX...",
"confirm_all": true,
"idempotency_key": "dsr-1234",
"audit_note": "DSR-1234 fulfilled"
}'Execute needs confirm_all
An erasure with a from_preview_id but no selector.memory_ids erases the entire scope — the
server requires confirm_all: true (or an explicit selector.memory_ids), else
422 EMPTY_SELECTOR_WITHOUT_CONFIRMATION.
Erasures cascade across all five layers and are recorded in the audit log with the audit_note;
tombstones are kept for governance.max_retention_secs so re-derivations can't reincarnate erased
data.
SIEM forwarding
[compliance.siem]
enabled = false # default off
format = "cef" # "cef" | "leef" | "json"
output_path = "/var/log/cortexdb/siem.log"
webhook_urls = []
datadog_api_key = ""
splunk_hec_token = ""
batch_size = 100
flush_interval_secs = 30Security-relevant events (auth success/failure, rate-limit hits, breach alerts, erasures, PII blocks,
capability denials) are formatted and shipped to the local log, every webhook_urls endpoint, Datadog,
and/or Splunk HEC. CEF is most widely supported in enterprise SIEMs; JSON suits cloud-native
aggregators.
Cross-agent visibility
[compliance.cross_agent]
enabled = false # default off
default_visibility = "private" # "private" | "tenant" | "global"With default_visibility = "tenant", agents within the same tenant scope see each other's memories by
default (useful for multi-agent systems). "private" isolates every agent, requiring explicit grants.
Deployment preset
export CORTEX_DEPLOYMENT_PRESET=dev_local| Preset | Intended for |
|---|---|
dev_local | Local development and evaluation. The default. |
on_prem_enterprise | Self-hosted, single-organization deployments. |
cloud_shared_saas | Multi-tenant shared cloud. |
cloud_private | Dedicated single-tenant cloud. |
These four are the complete set — older startup/growth/enterprise names are not valid. The preset
primarily shapes auth posture; self-hosted, the in-binary minter (/v1/auth/signup, /v1/auth/tokens)
is off by default (503 NOT_CONFIGURED) — enable it with CORTEX_V1_MINTER_ENABLE=1. Individual
controls are enabled by their own security.* / compliance.* sections. See
Deployment Presets.
A compliance-shaped checklist
Before shipping to a regulated environment:
governance.default_retention_ttl_secsset — plus a scheduled/v1/forget//v1/erasuresjob to actually delete (auto-expiry is not enforced).governance.pii_handling = "Block";governance.audit_logging = true.security.encryption.enabled = truewithkey_fileowned0600bycortexdb.security.tls.api_tls_enabled = trueor TLS terminated at a trusted LB;mtls_enabled = truefor inter-node RPC;min_tls_version = "1.3".security.rbac.enabled = truewith OIDC issuer set.- Hard rate limits enforced at your LB / gateway (server quotas are headers-only).
security.breach_detection.enabled = truewith awebhook_urlto your SOC.compliance.data_residency.enabled = trueif you have regional obligations;compliance.dsar.enabled = truefor GDPR/CCPA DSARs;compliance.siem.enabled = true.- A scheduled, verified cold backup shipped off-host with ≥30d retention and periodic test-restores.
Then start from the Enterprise profile.
Next steps
- Profiles & Presets — the full Enterprise profile
- Storage & Cluster — backups and durability
- Authorization — the role / capability model