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.
Most are off by default (PII detection and audit logging are on, and bearer auth is on unless you
set CORTEX_INSECURE_NO_AUTH=1) so you can evaluate quickly. Before
production, walk this page top to bottom and turn things on.
Retention is enforced; rate limits are not
A scheduled retention sweeper (every CORTEX_RETENTION_SWEEP_INTERVAL_SECS, default
3600 s) erases events older than the retention policy, and a non-null
[governance] default_retention_ttl_secs becomes the deployment-wide default it sweeps against (a
30-day value reports deployment_default_days: 30). Setting it therefore deletes data. See
Retention & WAL. Rate-limit quotas are reported in response
headers rather than enforced, so set hard limits at your load balancer.
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) | Deployment-wide retention default; the sweeper erases older events. |
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). Kept in memory and lost on restart unless CORTEX_V1_AUDIT_PATH names a file; see Audit Trail. |
Retention comes from policies; the audit path is /v1/audit
The deployment and per-scope retention policy is enforced by the sweeper (Retention & WAL).
Per-event values (ttl_seconds / expiration_date on an envelope) are stored with the event; expiry
comes from the retention policies, not from these fields. For selective deletion use
/v1/forget or /v1/erasures. The audit trail is queried at
/v1/audit — 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 — OIDC/RBAC not used; bearer auth still applies
default_role = "reader" # role assigned when RBAC is enabled
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, or as whatever X-Cortex-Actor names). It does not restrict the bind:
the server listens on 0.0.0.0, so publish the port on loopback only (-p 127.0.0.1:3141:3141). A keyless, network-exposed server auto-generates a key and
requires it rather than serving open, and an X-Cortex-Actor header with no bearer is rejected on
every preset; see Authorization.
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 limits are informational
The server reports rate-limit headers but doesn't reject over-limit requests, so enforce hard limits at
your load balancer or 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.
Record consent in your own system
There is no consent API to call; keep lawful-basis and consent records in your own system and apply them before you capture.
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_secs(or a per-scope policy) set, knowing the sweeper deletes events past it; dry-run the sweep first (Retention & WAL).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
Retention & WAL Maintenance
Operator-driven expiry of old events and reclamation of dead write-ahead-log rows — per-scope rules, dry-run sweeps, and the WAL census.
Token & Cost Ledger
GET /v1/admin/usage — durable per-role token and dollar accounting, what it measures exactly, what it only bounds, and what it refuses to price.