OperationsSecurity & compliance

Mnemosyne ships a set of security and compliance controls aimed at a SOC 2 Type II posture: a tamper-evident audit log, scoped RBAC, break-glass admin access, hashed and rotatable API keys, an IP allowlist, an mTLS boundary for federation, and region-locked data residency. This page describes what each control is and where it lives.

⚠️

Mnemosyne is v3-alpha. These controls are implemented in code, but no formal SOC 2 audit scope has been defined and no auditor has been engaged. The evidence-collection script is a template that produces stubs in the default alpha environment. Treat this as an engineering baseline, not a third-party-audited compliance certification.


SOC 2 control families

The controls matrix (docs/compliance/soc2-controls.md) groups controls into five families. Some are fully automated in code; others are documented but require operational execution by a human owner.

FamilyCoversExamples
Access ManagementAccess reviews, offboarding, break-glass, least privilege, IP allowlistQuarterly access reviews (manual); same-day API-key revocation + DB role removal
Change ManagementMigrations, migration-safety lint, canary deploy, DR restoreAll schema changes via migration files; canary deploy with automated rollback on SLO breach
Data Classification & RetentionSensitivity tiers and retention windowsMaps to the Sensitivity enum (public → internal → confidential → restricted)
Monitoring & LoggingAudit log, structured logs, slow-query logging, metricsMerkle audit chain; pino structured logs with request IDs; /metrics Prometheus endpoint
Vendor ManagementSub-processor registry and DPA trackingSee vendor management below

Retention windows (e.g. restricted data at 90 days) are documented in the controls matrix but not yet enforced in code. A scheduled purge job that respects these windows is tracked as future work; deletion behaviour is not silently changed.


Tamper-evident audit log

Access to customer data is recorded in an append-only audit log (mnemo_audit_log) that is made tamper-evident with a Merkle hash chain (MerkleAuditChain.ts). Each row is attributable — it records actor_id, actor_type, event_type, workspace_id, created_at, and ip_address.

The hash chain lets an auditor detect after-the-fact modification or deletion of log entries, which is what makes the log suitable as access-control evidence under the Monitoring family (CC4.1).


RBAC and the scope model

Authorization is enforced through scoped API keys. The server defines a fine-grained scope guard (getScopeForRequest in packages/server/src/auth.ts) covering 15 scopes — for example memory:read, memory:write, and memory:admin:cross_tenant. Keys are granted least privilege: a key only carries the scopes it needs, and the guard rejects any request whose scope is not present on the presenting key.


Break-glass admin access

Privileged, cross-tenant operations require break-glass access rather than a standing admin credential. A break-glass key (prefix mk_admin_) is gated on three conditions enforced in ScopedApiKeyAdapter.ts and the RBAC policy:

  • TOTP MFA — the operation must present a valid time-based one-time code (verifyTotp).
  • Short TTL — the grant is capped by ADMIN_MAX_TTL_MS (24h).
  • Two-person approval — a privileged operation needs a grant approved by a second, distinct admin. Requests are recorded in the mnemo_break_glass_request table.

API-key lifecycle and hashing

Workspace API keys are stored as Argon2id hashes, never in plaintext, so a database dump does not expose usable credentials. The key both authenticates the caller and pins the request to a workspace.

Keys are revocable for offboarding: POST /v1/admin/keys/:id/revoke removes a key and writes the event to mnemo_audit_log (the same-day offboarding control, CC6.2). The quarterly checklist includes auditing SELECT keyId, lastUsedAt, status FROM mnemo_api_keys and revoking unused keys.


IP allowlist

Sensitive keys can be restricted to a set of networks. ScopedApiKeyAdapter.ts performs an ipInCidr check (CIDR ranges) so that even a valid key is rejected when the request originates from an address outside the configured allowlist.


mTLS boundary for federation

Mnemosyne does not terminate mTLS in-process. The server runs on raw node:http and relies on a trusted TLS-terminating proxy (nginx, a cloud load balancer, or Fly.io’s edge) to perform the client-certificate handshake and pass the verified certificate via the x-mnemo-client-cert header.

The application trusts that header only when the request originates from the configured trusted-proxy IP (MNEMO_TRUSTED_PROXY_IP). The guard in packages/server/src/middleware/auth.ts compares the first X-Forwarded-For entry against that value; for any request that does not match, the header is ignored and the request falls through to other authentication. This is the boundary used for cross-instance federation peer traffic.

Two deployment requirements make the guard sound:

  1. The TLS terminator must strip or overwrite any client-supplied x-mnemo-client-cert before forwarding, so the proxy is the only source of the header.
  2. The Mnemosyne server must not be directly internet-accessible — all traffic flows through the trusted terminator (bind to 127.0.0.1 or an internal VPC address).

Data residency and region-lock

A workspace is pinned to a region at creation and the region is immutable — no API changes it. The MNEMO_REGION env var identifies which region a server instance serves; on every authenticated request the region middleware asserts workspace.region === MNEMO_REGION and returns 403 wrong_region (with the correct homeRegion) on mismatch. When MNEMO_REGION is unset the check is disabled (single-region back-compat mode).

Supported regions are us-east-1 (primary), eu-west-1 (EU), and ap-southeast-1 (APAC).

Cross-region federation is metadata-only. A peer in a different region receives { factId, hash, region } triples — never statement, content, or embedding — so content data does not cross a region boundary via federation (projectForPeer() in packages/federation/src/syncProtocol.ts).

To move a workspace between regions there is no in-place migration: create a new workspace in the target region, GET /v1/export from the source, POST /v1/import into the target, repoint API keys, then archive the old workspace.


Evidence collection

scripts/collect-soc2-evidence.sh automates evidence gathering for an audit window. Run it from the repo root:

bash scripts/collect-soc2-evidence.sh

It writes a soc2-evidence-YYYYMMDD/ directory with five artifacts: access logs (last 90 days of mnemo_audit_log), deploy history, an encryption-configuration snapshot, an incident-history index, and uptime metrics.

⚠️

In the default alpha environment the script produces stubs. When DATABASE_URL is unset it writes a stub instead of real access logs; when PROMETHEUS_URL is unset the uptime artifact contains only SLO-target text and manual instructions. Confirm every artifact contains real data — not a stub — before sharing the bundle with an auditor.

To capture real data, provide the connection variables:

DATABASE_URL="postgres://user:pass@host/db?sslmode=require" \
PROMETHEUS_URL="https://prom.internal" \
bash scripts/collect-soc2-evidence.sh

Vendor management

Vendors that process customer personal data are tracked in a sub-processor registry (docs/compliance/vendor-management.md) covering database hosting, cache, LLM/embedding providers, container hosting, source control/CI, observability, on-call alerting, and billing. Each entry tracks DPA status and an annual (Q4) review cadence.

DPA execution, Standard Contractual Clauses, and Transfer Impact Assessments are owned by Legal / DPO and are marked pending in the registry. For LLM vendors the registry calls for Zero Data Retention (ZDR), embedding-only mode where possible, and PII scrubbing before content is sent.