The key you hold
Every key that can decrypt your memories exists only inside a live request, derived from a credential you already carry. Not a promise not to look. An inability to.
The full hierarchy · static SVG · open the file
Four keys, and only one of them is yours to lose
The names in the diagram, said plainly.
Your account key. 256 bits of randomness, minted once and never changed for the life of the account. It is never stored in a form anything can use — only as wrapped copies.
Derived, on the spot, from the credential the request is already carrying: your password, your API key, your recovery code, your browser session. It exists to unwrap the DEK and then it is gone.
A fresh random key for every single record. The memory body is encrypted under its own CK; the CK is wrapped under your DEK. This is what makes sharing cheap later.
One key per team, wrapped once for each member under that member’s own DEK. Nobody hands anybody their personal key to work together.
The consequence worth sitting with: there is no master key, and no operator key. A dump of the database is a pile of ciphertext, wrapped keys, and salts. Nothing in it opens anything else in it.
The one thing you can genuinely lose is the last credential that can unwrap your DEK. That is what the recovery code is for, and why it is worth keeping somewhere real.
What is actually written down
One encrypted record is one string, in four fields.
AES-256-GCM, with the record’s own identity bound into the ciphertext as additional authenticated data. Move the bytes into another user’s slot and the decryption does not quietly return the wrong thing — it fails.
Tags, without the tag names
Search has to keep working, so the parts that make it work get their own treatment. A tag key is derived from your DEK and each tag is stored as an HMAC — the index sees opaque hex, never the word. The readable list of your own tag names lives as one more encrypted blob, decrypted in memory for the length of a request.
Vectors, in a basis nobody else knows
Semantic search means embeddings, and embeddings from a public model can be inverted back into approximate text. So before a vector is stored it is rotated by a transform derived from your DEK — an exactly orthogonal one, so distances and rankings are untouched and results are identical. The transform is never stored anywhere; it is regenerated from your key on each request. An exfiltrated index inverts to noise.
Sharing, without giving anyone your key
A team key that adds one field, and dies the moment someone leaves.
A team gets one TEK. It is wrapped once per member, under that member’s own DEK, so each member fetches the team key using the key they already have. When you share a memory, its content key gets a second wrap — under the TEK — added to the same envelope as a fifth field.
The body is never re-encrypted, and never copied. Roughly eighty bytes are added. A teammate reads it by unwrapping the TEK with their own key, unwrapping the content key from that fifth field, and decrypting the original record in place. Shared tags and shared vectors get the same treatment under the team key, so what is shared stays findable without becoming readable to the server.
Remove a member and the team rekeys: a fresh TEK, re-wrapped for everyone who stays, and a version bump that makes it live. The copy the departed member could unwrap is simply dead.
And if nobody has shared anything with you, none of this runs. The team path is gated on there actually being someone else’s shared content to find.
Where the line actually is
The guarantee is a precise one, and precision is worth stating in both directions.
What you get is an operator who holds no key. Your DEK is unwrapped inside the request that carries your credential, used, and dropped. There is no copy of it, no master key standing in for it, and no path by which we can read your memories — not for support, not for a subpoena served on the storage, not for anyone who walks off with a database dump.
What it is not is end-to-end encryption, and the reason is recall. Finding a memory by meaning requires an embedding of what it says, and that embedding is computed at the moment you save. Encrypting on your device instead would mean nothing could ever be indexed, and Muninn would become a filing cabinet you have to remember the name of — which is the product we deliberately did not build. So plaintext exists for the length of one request, in memory, and is never written down in the clear: not the body, not the preview, not the tag.
Unattended work is opt-in and explicit. A scheduled mirror to your own Drive or GitHub cannot read a thing unless you have connected one, which creates a separate wrapped copy of your key for exactly that purpose — and disconnecting removes it.
Metadata is not content. Sizes, timestamps and record counts are visible in a way ciphertext is not.
The plate at the top of this page was drawn by an AI working inside Muninn, stored there as an artifact, and published with a single link — which is the feature it happens to be describing. There is an animated version that builds the whole hierarchy in ten seconds, and it is live at a Muninn address, not on this site:
Same store, same keys, same envelope described above. An artifact is a memory that renders; publishing one gives it a public address without giving anything else away.