The security architecture, in full
This page is for engineers, security reviewers, and anyone who wants proof rather than promises. It describes exactly what cryptography runs where, what our servers store, and what an attacker (or we ourselves) could and could not learn from any component. For the plain-language version, see the help page.
Guarantees & threat model
The core property: vault plaintext and the keys that produce it exist only inside the user's browser. The server is an untrusted store of ciphertext. It authenticates you, schedules the switch, and relays sealed boxes; it is never in a position to open them.
We design against these attackers:
| Attacker | What they get | Outcome |
|---|---|---|
| Network eavesdropper | TLS-encrypted traffic carrying already-encrypted payloads | Nothing useful, two layers deep |
| Database thief | Ciphertext, salted Argon2id hashes, token digests | Cannot read vaults or log in |
| Malicious or compelled insider | Same database view, plus server code | Cannot read vaults without the user's password |
| Server compromise (full) | Database plus environment secrets | Recipient-escrowed vault keys only; see §7 |
| Stolen delivery email | A signed one-time link | Still blocked by the email code check |
The one attacker no architecture can beat is malware on the user's own device while the vault is unlocked. Section 8 covers how we shrink that window.
The key hierarchy
Everything descends from the master password through one deterministic, client-side pipeline. None of the intermediate values below ever leave the browser except the Auth Token (and that one, by construction, reveals nothing about the others).
- 1Master password + email + random salt
The password is concatenated with the lowercased account email and a random 16-byte per-user salt generated at registration. Binding in the email blocks cross-user rainbow tables; the salt blocks precomputation entirely.
- 2Argon2id → Stretched Master Key (32 bytes)
Argon2id with 64 MiB of memory, 3 iterations, 4 lanes, compiled to WebAssembly and executed in the browser. Memory-hardness is the point: GPU and ASIC password-cracking rigs lose their advantage when each guess costs 64 MiB of RAM.
- 3HKDF-SHA256 → two independent keys
The stretched key is expanded with two different info strings ("enc" and "auth") into a 256-bit Encryption Key and a 256-bit Auth Token. HKDF's extract-and-expand construction makes the two outputs computationally independent: possession of one yields nothing about the other.
- 4Encryption Key wraps the Vault Key
Each vault gets its own random 32-byte Vault Key from the platform CSPRNG. It is wrapped (AES-256-GCM) by the Encryption Key and only that wrapped form is stored server-side. The Encryption Key itself is imported as a non-extractable WebCrypto key and never serialised.
- 5Vault Key encrypts every item
Titles, fields, notes, tags, and file bytes are all separately encrypted with AES-256-GCM under the Vault Key before any network request is made.
Authentication without the password
Logging in must not require sending the password, so we send the derived Auth Token instead, and we treat even that token as a secret worth protecting at rest:
- The password never transitsThe browser sends only the HKDF "auth" output. Because HKDF is one-way and domain-separated, a server that records this value still cannot derive the Encryption Key from it.
- The token is hashed like a passwordThe server stores only an Argon2id hash of the Auth Token (64 MiB, 3 iterations, 4 lanes). A database leak therefore does not even let an attacker authenticate, let alone decrypt.
- Sessions are short and memory-onlyA successful login yields an HS256 JWT with a 60-minute default lifetime. It is held in a JavaScript variable, never in localStorage or cookies: a page refresh discards it and requires the master password again.
- Locking is immediate and totalThe Lock button drops the Encryption Key, every Vault Key, and the session token from memory in one step.
Encryption at rest
- AES-256-GCM everywhereEvery encrypted field uses AES-256 in Galois/Counter Mode with a fresh random 96-bit IV per encryption. GCM is authenticated: any bit flipped in storage or transit fails the tag check and decryption refuses, so tampering is detected, not silently decoded.
- Even the metadata is ciphertextItem titles, vault names, field labels and values, notes, and tags are all encrypted. The server cannot distinguish a crypto-wallet seed phrase from a cookie recipe, and neither can we.
- Files use the same key, streamedFile bytes are encrypted in the browser into a binary blob (12-byte IV followed by ciphertext and tag) before upload. Storage providers see opaque bytes with a random name; storage quotas are measured in encrypted bytes because that is all that exists server-side.
- Keys are non-extractableWorking keys are imported as non-extractable WebCrypto CryptoKey objects, so even script running in the page cannot export the raw key material through the WebCrypto API.
The split-key delivery escrow
A dead man's switch has a hard requirement that pure zero-knowledge cannot meet: the system must eventually hand a working key to a recipient without the owner present. We solve it by splitting that capability across two stores that live in different places, with the split performed in the owner's browser:
- 1A Delivery Key is born client-side
When you add a recipient, your browser generates a fresh random 32-byte Delivery Key for them. Each recipient gets their own; revoking one never affects another.
- 2The Vault Key is wrapped for them
Still in your browser, the Vault Key is encrypted under that Delivery Key. This wrapped copy (the escrow) is what the database stores. On its own it is just ciphertext.
- 3The Delivery Key is sealed separately
The Delivery Key travels to the server once and is immediately encrypted with AES-256-GCM under a key derived from a master secret that lives only in the server environment, never in the database.
- 4Only delivery reunites the halves
When the switch fires and a recipient passes both the signed link and the email code check, the server decrypts their Delivery Key and hands both halves to the recipient's browser. The Vault Key is reconstructed and the vault decrypted there, client-side. Vault plaintext still never exists on the server.
- Escrowed Vault Keys: ciphertext without their Delivery Keys
- Delivery Keys: ciphertext without the environment secret
- No path to any vault content
- A decryption key with nothing to decrypt
- No ciphertext, no escrows, no user data
- No path to any vault content
Delivery link hardening
Delivery links arrive by email, and email is hostile territory. So a link alone is never enough:
- Signed, not guessableEach link embeds the recipient id and a random 16-byte nonce, authenticated with an HMAC-SHA256 signature verified with constant-time comparison. Forging a link means forging the HMAC.
- The database stores only a digestWe keep a SHA-256 hash of each issued token, never the token itself. Someone who steals the database cannot reconstruct or replay any outstanding delivery link.
- Single use, short lifeA link is dead the moment it is claimed, and expires on its own after 72 hours (configurable) if never used.
- A second factor by constructionOpening the link only lets you request a 6-digit code, generated by a CSPRNG and sent to the recipient's email address. Only the code's SHA-256 hash is stored, and five wrong guesses lock the link permanently.
- Test deliveries can't leakWhen an owner runs a delivery test, the verification code is routed to the owner's own email, never to the real recipient, so rehearsals expose nothing.
Breach scenarios, honestly
The fairest way to evaluate a security design is to assume each layer falls and ask what the attacker actually walks away with:
| Scenario | Vaults without recipients | Vaults with recipients |
|---|---|---|
| Database stolen | Ciphertext only. Unreadable. | Ciphertext only. Unreadable. |
| Environment secrets leaked | Nothing. No data lives there. | Nothing without the database. |
| Database + environment together | Still unreadable. No escrow exists. | Escrowed Vault Keys recoverable. This is the residual risk described in §5. |
| Our staff, acting maliciously | Cannot decrypt. The math does not care about job titles. | Would need deliberate, combined misuse of both stores; equivalent to the row above. |
| Recipient's mailbox compromised | n/a | Attacker still needs the 6-digit code sent at claim time and has 5 attempts; the owner is emailed at every escalation stage long before delivery. |
| Your unlocked browser compromised | Game over for that session, as with any client-side encryption product. | Same. Lock early, lock often. |
Known limits & trade-offs
- No password recovery, everThere is no reset flow because there is nothing to reset against. We cannot rebuild what we never had. Losing the master password loses the vault; the dead man's switch itself is the recommended mitigation.
- The browser is the trust boundaryLike every end-to-end encrypted web app, the code that does the encrypting is served to your browser. A compromised device or a malicious extension operating while the vault is unlocked defeats any architecture. We shrink the window: 60-minute sessions, memory-only keys, nothing persisted, one-click lock.
- Escrow is opt-in exposureAdding a recipient creates the split-key escrow described in §5. Add recipients only to vaults you actually intend to deliver; vaults without them remain mathematically out of everyone's reach, including ours.
- Metadata about you existsWe necessarily know your email address, vault and item counts, encrypted sizes, recipient emails, and switch timing. We never know contents.
Every mechanism above is enforced in code, not in a policy document. The client-side cryptography runs in your own browser where you (or your security team) can inspect exactly what leaves the device.