Encryption
Your notes are encrypted in your browser before they are sent. Cloudflare stores ciphertext and wrapped keys, and cannot read either.
One data key, protected twice
Section titled “One data key, protected twice”Passphraseonly in your head
Argon2id
64 MiB, t=3
64 MiB, t=3
KEKkey-encryption key
AES-GCM wrap
wrappedDekstored in D1
Recovery codeon paper, kept apart
HKDF-SHA256
RKEKrecovery key
AES-GCM wrap
recoveryDekstored in D1
Data key (DEK)random 256-bit, in memory only
AES-256-GCM
AAD = note:<id>
AAD = note:<id>
Note ciphertexttitle, body, labels and color, stored in D1
Orange: secrets that never leave your browser. Dashed: what the server stores.
- A random 256-bit data key (DEK) encrypts every note.
- Your passphrase is stretched with Argon2id into a key that wraps the data key.
- Your recovery code is turned into a second key that wraps the same data key.
Either secret can unwrap the data key. That is why the recovery code can rescue your notes, and why changing the passphrase doesn’t re-encrypt anything.
What the server can and cannot see
Section titled “What the server can and cannot see”| Encrypted (inside the ciphertext) | Plaintext (so notes can be sorted) |
|---|---|
| Title | pinned |
| Body | archived |
| Labels | trashed_at |
| Color | position and timestamps |
Protections
Section titled “Protections”- Your passphrase, the data key and note content never leave the browser unencrypted.
- In a session the data key is a non-extractable
CryptoKeykept in memory only. Locking discards it. - The offline copy on your device holds the same ciphertext and wrapped keys as the server. Signing out deletes it.
- Each note’s ciphertext is bound to its id, so the server can’t swap content between notes.
- The client rejects Argon2 parameters below a safe floor, so a malicious server can’t downgrade the key derivation.
- The Worker verifies the Cloudflare Access JWT itself (signature, issuer, audience) instead of trusting headers.
- A strict Content Security Policy ships with the app. The Markdown renderer escapes raw HTML and drops executable URLs.
The algorithms
Section titled “The algorithms”| Step | Algorithm |
|---|---|
| Passphrase → key-encryption key | Argon2id (64 MiB, t=3, p=1) |
| Recovery code → recovery key | HKDF-SHA256 |
| Wrapping the data key | AES-256-GCM |
| Each note | AES-256-GCM with a random 96-bit IV and AAD note:<id> |