CortexDB Docs
Operations

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
FieldDefaultWhat it does
default_retention_ttl_secsnull (forever)Deployment-wide retention default; the sweeper erases older events.
max_retention_secs7 yearsHard upper bound, even for explicit "forever".
pii_detectiontrueScan 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_loggingtrueLog 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 blobs

When 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.key

For 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 = false

When 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 capacity

Rate 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:00Z

Breach 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 alerts

Failed 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 role

When 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 = false

When 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.

[compliance.consent]
require_consent = false                  # default — no consent enforcement
default_purposes = ["customer_support"]
max_validity_secs = 31536000             # 1 year default

When 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 level

With 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 off

When 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 = 30

Security-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
PresetIntended for
dev_localLocal development and evaluation. The default.
on_prem_enterpriseSelf-hosted, single-organization deployments.
cloud_shared_saasMulti-tenant shared cloud.
cloud_privateDedicated 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 = true with key_file owned 0600 by cortexdb.
  • security.tls.api_tls_enabled = true or TLS terminated at a trusted LB; mtls_enabled = true for inter-node RPC; min_tls_version = "1.3".
  • security.rbac.enabled = true with OIDC issuer set.
  • Hard rate limits enforced at your LB / gateway (server quotas are headers-only).
  • security.breach_detection.enabled = true with a webhook_url to your SOC.
  • compliance.data_residency.enabled = true if you have regional obligations; compliance.dsar.enabled = true for 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

On this page