Security & tokens
Memoturn’s isolation story has two layers: structural — a profile is its own database file, and no data-plane operation can touch two profiles — and token-based — short-lived Ed25519-signed JWTs that widen authorization only, never the data plane’s reach.
Auth modes
Section titled “Auth modes”Auth is off by default in dev; the node logs a loud warning at startup. Enable it for any shared deployment:
MEMOTURN_AUTH=on MEMOTURN_PLATFORM_KEY=... memoturndWith auth on, the posture is fail-closed: the node refuses to start without
MEMOTURN_PLATFORM_KEY — there is no generated platform key to leak or lose. It signs
per-database JWTs with an Ed25519 key; signing-key precedence is MEMOTURN_AUTH_KEY (base64
PKCS#8 — in production, mounted from a Kubernetes Secret) → a local file under the data dir →
object storage (only with MEMOTURN_PERSIST_AUTH_KEY=1, which persists a generated key
unencrypted — the mounted Secret is the preferred path) → generate. A multi-replica fleet
must share the signing key, or pods reject each other’s tokens. See
Configuration for all auth variables.
Token kinds
Section titled “Token kinds”| Token | Covers | Posture |
|---|---|---|
| Per-database token | exactly one profile’s database | the agent posture — an agent is locked to the one profile it serves |
Namespace token (ns claim) | every profile under one namespace, including its control routes | the orchestrator posture — mint per-profile tokens, checkpoint memories, list profiles |
| Platform key | control-plane operations: provision databases, mint tokens | operators and provisioning code only |
| Cluster key | node-internal hops (write forwarding) | never leaves the cluster |
Scopes are read, write, and admin: recall and get need read; ingest, forget, and
session-end need write. Tokens are short-lived (default TTL 3600 s).
The cluster key must differ from the platform key — they are separate trust boundaries, and the node refuses to start if they match. Unset, it is derived from the signing key, so it is identical on every node in the fleet with no extra secret to manage.
# per-database token: one agent, one profilememoturn --platform-key ... token create acme--alice --scope write
# namespace token: every profile under acme — the orchestrator posturememoturn --platform-key ... token create-ns acme --scope write --ttl 3600The same mints are available over HTTP (POST /v1/databases/{db}/tokens,
POST /v1/namespaces/{ns}/tokens) — see REST API.
Stateless verification
Section titled “Stateless verification”Gateways verify JWTs statelessly with the Ed25519 public key — no auth-service round-trip on the data path. Nodes additionally enforce lease epochs on every write, so a stolen-but-valid token still cannot make a zombie writer dangerous (see Consistency).
Structural isolation
Section titled “Structural isolation”A profile boundary is a database file. There is no query, filter, join, or recall operation that spans two profiles — the isolation is not a row-level policy that could be bypassed, it is the shape of the storage. A namespace token widens which profiles a caller may address, one at a time; it does not create any cross-profile operation. See Profiles.
A further structural guard: per-tenant object-store prefixes — each database’s segments and manifests live under its own prefix in the bucket.
The SQL guard
Section titled “The SQL guard”The SQL escape hatch (POST /v1/db/{db}/sql) is walled in:
- Reserved tables are unreachable. Everything Memoturn manages inside a database —
__memoturn_kv,__memoturn_docs_{collection},__memoturn_memories*— carries the__memoturn_prefix and cannot be referenced from user SQL, however the name is quoted. - Read tokens cannot mutate. Mutating statements need
writescope; a read-scoped token gets403. - No sandbox escapes.
ATTACH,VACUUM INTO, andPRAGMA writable_schemaare rejected. Benign read-only PRAGMAs (integrity_check,table_info) still work. - No transaction control.
BEGIN,COMMIT,ROLLBACK,SAVEPOINT, andRELEASEare rejected (trigger bodies excepted): a statement batch is already one atomic unit, and the engine owns transaction boundaries.
Token revocation
Section titled “Token revocation”Deleting a database — or the profile it backs — writes a deletion tombstone. Write tokens minted
before the deletion are rejected with 403 (token revoked: it predates this database's deletion; mint a fresh token), so a stale token cannot resurrect a re-created profile of the
same name.
Network security in Kubernetes
Section titled “Network security in Kubernetes”The Helm chart is hardened by default (see Deployment):
- Secure-by-default pods — non-root (uid 65532), read-only root filesystem, all Linux
capabilities dropped, RuntimeDefault seccomp; writable paths are explicit
emptyDirmounts. - No Kubernetes API access — a dedicated ServiceAccount with no API token mounted.
- NetworkPolicy — egress locked to DNS, the object store, and (optionally) HTTPS for real
S3 and AI providers; ingress restricted to the HTTP port, tightenable with
allowExternalIngressandextraIngressFrom. - Secrets via Kubernetes Secrets (External Secrets Operator compatible for enterprise). The
chart consumes
auth.existingSecret(keysPLATFORM_KEY,CLUSTER_KEY, plusAUTH_KEYfor multi-replica fleets) and an optionalai.existingSecretfor extraction/embedding keys.
TLS at the ingress (cert-manager) and JWT verification at the gateway remain part of the full cell design.
Data residency
Section titled “Data residency”A database’s primary region is chosen at creation. Regions are independent cells — each with its own etcd, gateways, data plane, and regional object-storage bucket — so the cell model maps directly onto residency requirements: a profile created in the EU cell stays in the EU cluster and its EU bucket. The global control plane sits on the provisioning path, never the data path.
Data governance policies
Section titled “Data governance policies”Enterprises can enforce data-handling rules per namespace with a governance policy — a JSON document set with the platform key and enforced on every node:
curl -X PUT $MEMOTURN_URL/v1/namespaces/acme/policy \ -H "Authorization: Bearer $PLATFORM_KEY" \ -d '{"policy": { "retention": {"pitr_secs": 3600}, "memory": {"task_ttl_max_secs": 600, "superseded_max_count": 20}, "ai_egress": {"extract": "deny", "embed": "self_hosted_only"} }}'- Retention caps tighten the node’s point-in-time-recovery windows for every profile under the namespace; memory rules cap task TTLs (clamped at ingest) and age out superseded history and old events automatically.
- AI egress rules govern the optional AI features per tenant:
extract/askset todenyreturn a deterministic403before any model is called;embedset todenydegrades exactly like an unconfigured embedder (writes succeed, keyword recall keeps working), andself_hosted_onlypermits embedding only through a self-hosted endpoint (loopback, private-network, cluster-internal, orMEMOTURN_EMBED_SELF_HOSTED_HOSTS). - Profiles can tighten, never loosen:
PUT /v1/memory/{ns}/{profile}/policy(admin token) accepts only overrides at least as strict as the namespace policy — a loosening override is a409naming each offending field. The effective policy is always the strictest of node config, namespace, and profile. - Policies live in object storage next to the data they govern and converge on every node within the policy cache window (default 30 s) — no restarts. Egress checks fail closed; retention and TTL clamps never block writes.
See Configuration and the CLI.
Audit logging
Section titled “Audit logging”With audit.enabled in the namespace policy, every node records an append-only, per-namespace
audit stream in object storage — durable, immutable objects, deliberately outside branching so
a rewind can never erase the trail, and surviving even deletion of the profiles it describes.
- What’s recorded: memory mutations (ingest, extract, forget, session end) with
txidand counts; every AI egress with provider, model, endpoint, item/byte counts, and duration — including denials; token minting, policy changes, and database deletion. Reads (recall, get, ask) are recorded only withaudit.include_reads. - Metadata only: events never contain memory content, transcripts, or credentials. Each event carries actor attribution — a non-reversible hash of the credential plus its scope and claims — so an auditor can correlate “same token” without the stream ever holding material that grants access.
- Reading the trail:
GET /v1/namespaces/{ns}/auditwith time/action/profile/outcome filters and cursor pagination — platform key, or a namespace admin token for its own stream, so enterprises pull their trail without holding the platform key. The CLI exports ranges as JSONL:memoturn audit export acme --from 7d --action ai. --outcome denied. - Bounds: emission is non-blocking and off the write path; a crash loses at most one flush
window (
MEMOTURN_AUDIT_FLUSH_MS, default 2 s; orderly shutdowns drain).audit.retention_secsbounds the stream itself. For tamper evidence, pair the stream with bucket-level object lock.
Verifiable erasure
Section titled “Verifiable erasure”Forget hides a memory; erasure proves it’s gone — including from point-in-time-recovery
history. POST /v1/memory/{ns}/{profile}/erasures targets one memory, a topic’s whole
supersession chain, or a session:
curl -X POST $MEMOTURN_URL/v1/memory/acme/alice/erasures \ -H "Authorization: Bearer $TOKEN" \ -d '{"topic_key": "user.home-address", "type": "fact"}'# 202 { "erasure_id": "ers_…", "status": "pending", "txid": 412, "grace_until": … }- Immediately: the rows, search entries, and vectors are hard-deleted with
secure_deletepage zeroing, and the post-erasure state is durably shipped before the request returns. - After the grace window (
erasure.grace_secsin the policy, default 24 h — the undo window), the node rewrites object-storage history: every restorable snapshot and segment below the erasure point is dereferenced and physically reclaimed. Completion is bounded by grace + maintenance cadence, never the 30-day snapshot tier. - Then it proves it: object keys encode their transaction ids, so absence is verifiable by listing. A completed erasure carries a signed Ed25519 receipt — target, erasure point, and the verification evidence — checkable offline against the cluster’s public key.
- Honest blockers: a named checkpoint pinning older history, or a branch that may still
hold the data as live content, flips the erasure to
blockedwith the offenders named — never silently violated. Receipts scope their claim to object storage (the source of truth); node-local caches are transient and converge. erasure.purge_on_forget: trueupgrades every plain forget into a tracked erasure (the coupon id rides theMemoturn-Erasure-Idresponse header). PollGET .../erasures/{id}ormemoturn memory erasuresfor the receipt; erasure requests and completions land in the audit stream.
Operational notes
Section titled “Operational notes”- Without
MEMOTURN_ETCD, a node that looks multi-node — auth on, or a non-loopbackMEMOTURN_ADVERTISE— refuses to start unlessMEMOTURN_SINGLE_NODE=1: the in-process lease table cannot enforce single-writer across nodes. - Per-tenant encryption keys wrapped by cloud KMS are planned for enterprise — see Roadmap.