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.
| Family | Covers | Examples |
|---|---|---|
| Access Management | Access reviews, offboarding, break-glass, least privilege, IP allowlist | Quarterly access reviews (manual); same-day API-key revocation + DB role removal |
| Change Management | Migrations, migration-safety lint, canary deploy, DR restore | All schema changes via migration files; canary deploy with automated rollback on SLO breach |
| Data Classification & Retention | Sensitivity tiers and retention windows | Maps to the Sensitivity enum (public → internal → confidential → restricted) |
| Monitoring & Logging | Audit log, structured logs, slow-query logging, metrics | Merkle audit chain; pino structured logs with request IDs; /metrics Prometheus endpoint |
| Vendor Management | Sub-processor registry and DPA tracking | See 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_requesttable.
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:
- The TLS terminator must strip or overwrite any client-supplied
x-mnemo-client-certbefore forwarding, so the proxy is the only source of the header. - The Mnemosyne server must not be directly internet-accessible — all
traffic flows through the trusted terminator (bind to
127.0.0.1or 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.shIt 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.shVendor 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.