Skip to content

End-to-end encryption

End-to-end encryption (E2EE) means the server can’t read your data. Encryption and decryption happen on your devices; the server stores only ciphertext and wrapped key material.

For the server-side architecture (endpoints, entities, workflows), see E2EE architecture.

When E2EE is enabled, these record types are encrypted client-side before upload:

  • Memory — your personal knowledge store
  • Sessions — optimization history
  • Providers — your AI provider configurations (with API keys)
  • Todos — task list

What’s not encrypted, by necessity:

  • Account metadata (your email, role, account creation time).
  • Record metadata: per-record timestamps, IDs, owning user, entity type.
  • Sync cursors and the audit log.
  • The shape of your traffic — which endpoints you hit, when, from where.
  • What a user hands to a background assignment — the prompt, the records they picked, and the artifact that comes back. That plane is plaintext by construction, because whatever runs the work has to read it; what bounds it is tenant isolation plus a retention window measured in hours, not encryption. It is the one route family that never travels over /api/sync/*, and it moves nothing on its own — a user affirms each set of records by name. See the Mesh trust model.

E2EE protects content; it does not give you anonymity from the server operator.

Three layers of keys, all 32 bytes:

Key What it does How it’s stored
UMK — User Master Key Wraps all DEKs. Identifies “you” cryptographically. Generated locally; stored in DPAPI on Windows; never uploaded in plaintext
DEK — Data Encryption Key One per record. Encrypts the payload. Wrapped with UMK and stored next to the ciphertext
KEK — Key Encryption Key Derived per-purpose: ECDH for device-to-device, Argon2id for recovery Ephemeral; never stored

Every encryption uses AES-256-GCM with AAD context binding — a string that ties a wrapped blob to its context. A blob from one context can’t be silently replayed in another:

Purpose Algorithm AAD
Record payload AES-GCM(DEK, plaintext) pia-e2ee-v1:{userId}:{entityType}:{entityId}
DEK wrap AES-GCM(UMK, DEK) pia-dek-wrap-v1:{entityType}:{entityId}
Device-to-device wrap AES-GCM(KEK, UMK), KEK from ECDH pia-umk-wrap-v1:{targetDeviceId}
Recovery wrap AES-GCM(KEK, UMK), KEK from Argon2id

Each device has its own ECDH (P-256) and ECDSA (P-256) keypairs, generated locally via Windows CNG. The first device on an account self-wraps the UMK using its own keys. Each additional device must be approved from a device that’s already active:

  1. The new device registers as Pending and uploads its public keys.
  2. An existing device sees the request and confirms (you compare a 16-character fingerprint shown on both screens — XXXX-XXXX-XXXX-XXXX).
  3. The existing device wraps the UMK for the new device using ECDH.
  4. The new device unwraps it locally and is upgraded to Active.

If you don’t have an existing device handy, the recovery code is the way back in — see Recovery codes.

DEKs, KEKs, and unwrapped UMK material are zeroed in memory immediately after use on the client. Plaintext keys never touch disk except via DPAPI for the UMK at rest.