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. 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
FieldDefaultWhat it does
default_retention_ttl_secsnull (forever)TTL applied to events with no explicit retention.
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).

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 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 — 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 = 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; 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 capacity

Rate-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: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.

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 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 set — plus a scheduled /v1/forget / /v1/erasures job to actually delete (auto-expiry is not enforced).
  • 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