The deep overview. Every box, every arrow.
The privacy page gives you the short version. This is the long one: the actual architecture, the exact parameters, the parts that protect you and the parts that can't. If you're a security researcher, a journalist deciding whether a source can use Mune, or an engineer deciding whether the word encrypted is earned here, this page is for you.
Written to be checked. And dated.
Two things about how this page works. First, it describes the system as built, verified against the code on 2026-07-11. When the code changes in a way that matters, this page changes in the same commit. Second, where the honest answer is unflattering, the unflattering answer is what you'll read. There's a section on what the encryption cannot protect you from, because a threat model that only lists wins is an ad.
The engineering version of this document lives in the repository as docs/architecture/passphrase-and-recovery.md. The repository isn't public yet, so this page doesn't point at it and call that transparency. It carries the content.
One system, two paths.
Everything on this page gets easier if you hold one mental model: your words move through Mune on two different paths, with two different rules.
Path one · Storage
Your device
The words exist in plaintext only in memory, while you write.
Encrypted on device
AES-256-GCM under your data key, a fresh 96-bit IV per payload.
Supabase
Stores ciphertext and IV. Holds no key that opens either.
Everything durable takes this path: chat messages, journal entries, session titles and summaries, memory notes. The server can hold it and cannot read it.
Path two · The AI request
Your device
The conversation travels as plaintext over TLS, because the model has to read it to answer.
Mune's server
Authenticates you, attaches no name, email, user id, or client IP, and forwards.
Anthropic
Reads the message, streams a reply, does not train on it, deletes it on its standard 30-day schedule.
Readable in flight, by design. Both sides of the exchange are then encrypted on your device and stored via path one.
The storage path is the locked box from the privacy page. Words are encrypted on your device, under a key only your device holds, before any network call. What travels and what lands in the database is ciphertext. This covers chat messages, journal entries, session titles and summaries, and memory notes, on web and on the phone.
The AI path is different, and Mune doesn't pretend otherwise. A model that cannot read your message cannot answer it. When you talk to the AI, the conversation crosses to Anthropic as plaintext, in flight, over TLS, and comes back the same way. Section five is precise about what that window contains, what it excludes, and how long the other side keeps it.
One consequence worth stating early, because getting it wrong would be convenient: your past chats are stored. When you reopen an old session, the transcript was fetched from the server as ciphertext and decrypted on your device. The server keeps encrypted copies. It never keeps readable ones.
Three keys, one of which touches your words.
When you finish onboarding, your device generates a random AES-256 key called the DEK, the data encryption key. Random matters. The DEK is not derived from your passphrase, so nothing about you makes it guessable. It encrypts every chat message and every journal entry with AES-256-GCM, a fresh random 96-bit IV per payload, and it never leaves your device in unwrapped form.
Your passphrase never encrypts content directly. It runs through PBKDF2 (SHA-256, 310,000 iterations, a random 16-byte salt) to derive the KEK, a key that can only wrap and unwrap other keys. The KEK wraps the DEK using AES key wrap, RFC 3394, and the resulting 40 bytes of key-wrap ciphertext is what the server stores. The KEK itself exists in memory for the length of one operation and is never persisted anywhere.
The recovery phrase is the same construction on a second track. It's a 24-word BIP39 phrase, 256 bits of entropy, with a built-in checksum so a typo fails at the prompt instead of after a round trip. It derives the KRK through the same PBKDF2, using a second, independent salt, and wraps a second copy of the same DEK. The two salts are deliberate: an attacker who steals the database can't reuse the work of guessing at your passphrase to guess at your recovery phrase.
Why this shape. Two wrapped copies of one key mean changing your passphrase is a single rewrap, one small write, with none of your content re-encrypted. They also mean a forgotten passphrase is recoverable from the 24 words without resetting the account. And they set the failure mode, which onboarding states out loud: lose both, and the writing is gone. There is no reset that brings it back, because a reset that could would be a back door.
Your passphrase
never stored, never sent
In your memory. The everyday way in.
KEK · key encryption key
Can only wrap and unwrap. Exists in memory for the length of one operation.
Wrapped DEK, passphrase copy
stored on the server
40 bytes of key-wrap ciphertext.
Your 24-word recovery phrase
never stored, never sent
On paper or in a password manager. The one backup.
KRK · key recovery key
Same rules as the KEK: wrap and unwrap, memory only.
Wrapped DEK, recovery copy
stored on the server
A second wrapped copy of the same key.
DEK · data encryption key
unwrapped only on your device
A random AES-256 key generated on your device at onboarding. Not derived from your passphrase, so not guessable from anything about you.
Your words, as ciphertext
stored on the server
Every chat message and journal entry. Unreadable without the DEK.
Ciphertext, salts, and shapes.
From where the server stands, the content of the database is a blob: encrypted, unreadable cargo. Here is the full inventory, table by table, including the parts the server can read. The readable columns are the cost of running a service at all.
| Table | Encrypted | Readable by the server |
|---|---|---|
chat_messages | The message content | Role (you or the AI), which session it belongs to, timestamp |
chat_sessions | Mood, title, and summary, as one encrypted bundle | Status, message count, timestamps |
journal_entries | Title and body, as one encrypted payload | How it was created (manual, assisted, from chat), an optional session link, timestamps |
user_memory_nodes | The content of each memory note | A category label per note |
user_keys | Two wrapped copies of the DEK | The two salts and the KDF parameters |
feedback | Nothing. Plaintext by design | The feedback text, stored without any account identifier |
What is never stored, anywhere on the server side: your passphrase, your recovery phrase, the DEK, the KEK, and the KRK. Not in the database, not in logs, not at the login provider. The login provider (Clerk) holds your email, your account password hash, and your app preferences, and holds no key material at all.
Every user table has row level security enabled with a deny-all default. Each read and write is checked, per operation, against the identity in your signed login token, and a follow-up migration closed the subtler hole too: you can't attach a message to someone else's session, because ownership is enforced through the relationship as well.
And the plain reading of the third column: metadata is not nothing. The server can't read a word you wrote, and it can still see that you wrote at 3am, how often, and roughly how much. Timestamps, message counts, and ciphertext lengths are visible to any storage system that isn't doing padding and cover traffic, and Mune isn't. That trade is in section seven, not hidden under it.
Unlock, recovery, and what your browser keeps.
You hold three different secrets, and they do different jobs. The account password, managed by the login provider, gets you through the front door and is resettable by email like any account password. The passphrase that unlocks your writing is separate, and nobody can reset it, including me. The 24-word recovery phrase is the one backup for that passphrase. Copy that blurs these three does damage, so this page keeps them apart.
Unlocking is an attempt. Your device fetches the wrapped key row, derives the KEK from what you typed, and tries to unwrap. A wrong passphrase fails the unwrap cryptographically. There's no password check on a server, because the server has nothing to check against.
An unlocked tab survives a page reload without re-asking for the passphrase, and here is exactly what that costs. On unlock, the browser generates a wrapping key that lives in IndexedDB and is marked non-extractable, which means the browser will use it on request but won't hand its raw bytes to any script. The DEK is wrapped under that key, and the wrapped bytes sit in sessionStorage, keyed to your user id. Close the tab and sessionStorage is gone, which makes the wrapped copy permanently useless. The passphrase, the recovery phrase, the KEK, and the KRK never persist at all, in any storage, for any duration.
Recovery is deliberately a one-way gate. The 24 words are checksum-validated on your device before anything is sent. A successful recovery unlock puts the app in a holding state: you must set a new passphrase before anything else is reachable, and nothing persists until you have. Otherwise a refresh could carry someone past the rewrap step.
On the phone, the same wire formats are produced by a parallel implementation, held byte-compatible by cross-checked test vectors, because the iOS runtime lacks the browser's crypto API. By default the unlocked key lives in memory only, so a cold start asks for the passphrase again. Opting into biometric unlock seals the key in the device Keychain behind the operating system's own biometry check, marked device-only so it never syncs to iCloud, and the app relocks after 60 seconds in the background.
The part where honesty costs something.
To answer you, the model has to read you. Every AI request leaves your device as plaintext over TLS, lands at Mune's server, and is forwarded to Anthropic. The forwarding happens server-side, so the request Anthropic receives comes from Mune's infrastructure, carrying Mune's network address, not yours. Your IP never reaches the model provider, and neither does your name or email, because the server never puts them in the request.
The request contains exactly five fields: the model name, a token budget, a temperature, the system prompt, and the messages. No user id, no account metadata, no fingerprint. That's not a policy sentence, it's a test: the codebase carries a regression test that fails if a user identifier ever appears in the serialized request.
So Reflect is pseudonymous at the metadata layer, not anonymous. Written reflection is intrinsically identifying. If you tell the model you're a nurse in Oslo with a younger sister, no absent user id makes that sentence anonymous. The architecture strips every identifier it controls. It cannot strip you out of your own words, and claiming otherwise would be the exact kind of promise this page exists to avoid.
What happens on Anthropic's side is governed by their commercial API terms: your messages are not used to train models, and inputs and outputs are deleted from their systems within 30 days. Content their safety systems flag can be held longer. Mune does not have a zero-retention agreement with Anthropic today. If that changes, this paragraph changes. I'd rather tell you 30 days than promise you zero and be wrong.
If you turn personalization on, Reflect includes your saved memory notes in the request so the conversation can be less generic. Those notes are decrypted on your device, travel inside the prompt, and sit under the same retention window, where their stability across sessions makes your requests more linkable than the metadata alone would be. That is the trade personalization makes. The toggle exists so it's yours to make.
Who this stops, and who it doesn't.
Both columns, because a threat model with one column is marketing.
Defended against
- A database breach. The attacker gets ciphertext, wrapped keys, and salts. Reading content means an offline guessing attack against your passphrase through PBKDF2, or against 256 bits of recovery-phrase entropy, which is not a real attack.
- A curious or compelled operator, me included. The most a subpoena extracts is what I can read: ciphertext, your email, timestamps, and the shape of your account. Not the words.
- A breach at the login provider. Clerk holds identity, not keys. A full Clerk compromise yields zero key material.
- Another user. Row level security checks every operation against the owner, including through relationships between rows.
- Yesterday's attacker. A script injected into a past, closed session has nothing durable to steal. The wrapped key dies with the tab, so compromise doesn't outlive it.
Not defended against
- Your own device. A keylogger, a malicious browser extension, or a person holding your unlocked phone sits below the encryption and sees what you see.
- Code running in a tab while it's unlocked. Unlocked means the key is usable. An attacker who gets script into the page during a live session can read what the session can read, until the tab closes.
- The AI window. Plaintext crosses to the model provider on every AI request and sits under their standard 30-day deletion. Encryption at rest does nothing here, by design.
- A weak passphrase. 310,000 PBKDF2 iterations slow an offline attack. They don't rescue a guessable passphrase.
- Losing both the passphrase and the recovery phrase. The data is unrecoverable, on purpose, and no support ticket changes that.
- A malicious future version of Mune itself. You trust the code your browser downloads today. A hostile deployment could capture passphrases. This is true of every encrypted web app, and the honest ones say so.
Known limits, stated plainly.
Mune is pre-alpha software built by one person, and this section is the part a glossier page would cut. These are real, current properties of the system. They're on the list, and when one changes, this page changes.
There is no forward secrecy. One data key encrypts everything, for the life of the account. A key compromised once reads everything it ever encrypted. Per-entry keys or key epochs would narrow that, at real complexity cost, and haven't been built.
Ciphertexts aren't yet bound to their context. Someone with write access to the database couldn't read or forge your content, but could in principle swap encrypted payloads between rows of the same account and have them still decrypt. Binding each ciphertext to its row and role (additional authenticated data) closes this and is on the tracker.
Rate limiting on the API routes is in-memory and per-server-instance: real protection against casual abuse, not a global guarantee. An unlocked web tab stays unlocked as long as it's open; there's no inactivity timeout yet. And the storage metadata described in section three, timing, counts, and sizes, is visible to the server and to anyone who compels it.
Don't take this page's word either.
The claims above are checkable from the outside, and checking them is the point of publishing this.
Open your browser's developer tools, network tab, while saving a journal entry or sending a chat message. The write that leaves for the database carries base64 ciphertext and an IV. Search the request bodies for any phrase you just wrote. It isn't there.
Visit /diagnostics. It's a public self-test page that runs the encryption primitives live in your own browser: key generation, wrap, unwrap, encrypt, decrypt, and shows each step passing or failing on your device.
Read the pages where you write. No analytics, no error-tracking script, no third-party code loads there at all. The marketing pages, including this one, carry cookieless page analytics, and that's the whole inventory.
When the repository opens, the document to read first is docs/architecture/passphrase-and-recovery.md, the engineering source of this page, down to the migration SQL and the test plan.
And if you break something, or you're a journalist with a scenario this page doesn't answer, write to me: fredrik@mune.so. A found hole is worth more to Mune than a compliment.
The short version of this page is the privacy page. The short version of the privacy page is one sentence: the server holds what you wrote, and cannot read it. Everything above is what that sentence costs, and what it buys.