Vocabulary
Every name this document uses, defined once. None of them are needed to use Vault3; they are defined here so that nothing below has to be taken on trust.
- zero-knowledge
- The server holds your data without ever holding the means to read it. This is a consequence of where the encryption happens, rather than a policy commitment.
- ciphertext
- Your information after it has been encrypted: same size, no recoverable meaning without the key. It is the only version of your vault the server holds.
- AES-256-GCM
- The cipher itself, and the standard choice for this job. The GCM part adds authentication: if a single byte of an encrypted item changed in the database, your browser would reject it rather than display altered data.
- Argon2id · 64 MiB
- A deliberate slowdown around your Master Password. Every guess has to fill 64 MiB of memory first: negligible once on your laptop, and the dominant cost for anyone attempting billions of guesses on rented hardware.
- PBKDF2-SHA-512 · 1,000,000
- The same idea in a second, older, standards-approved form, used as a backstop: a million rounds of arithmetic your password passes through before it becomes a key. With two independent slowdowns, neither has to be perfect.
- HKDF-SHA-512
- How one secret becomes several separate keys. The results are siblings rather than copies: holding one reveals nothing about the others, and the derivation runs in one direction only.
- master unlock key
- The single key your two secrets combine into, and the root of every other key. It exists only for the duration of the derivation and is never stored, sent or persisted, including in the browser.
- post-quantum
- The kind of mathematics a future quantum computer is expected to break is not used here at all, so nothing stored today becomes readable when one arrives.
The key hierarchy
Two secrets go in, one root key comes out, and every other key descends from it, all inside the browser before anything is sent. The security page draws this in plain language; here it is with every stage, width and cost named.
the key hierarchy
Master Password (in your head) Secret Phrase (12 words = 132 bits) │ │ │ PBKDF2-HMAC-SHA-512 × 1,000,000 │ HKDF-SHA-512 │ Argon2id · 64 MiB · 4 passes · 4 lanes│ salt = your email ▼ ▼ passKey (64 bytes) ────── XOR ────── phraseKey (64 bytes) │ ▼ master unlock key (64 bytes — never stored, never sent) │ ┌───────────────┴───────────────┐ ▼ ▼ authentication key (64 B) encryption key (32 B) sent · stored Argon2id-hashed never leaves your device │ unwraps ▼ vault key (32 B, one per vault) │ unwraps ▼ item key (32 B, one per item) │ seals · AES-256-GCM ▼ overview blob + details blob
Why the two branches cannot help each other
The authentication key and the encryption key are separate expansions of the same root, not two copies of it. Holding the key the server stores, or an entire database dump of them, yields nothing about the key that opens your vault. The derivation runs in one direction only.
Why the phrase takes the fast path
Your Master Password gets two expensive stages because it is short and guessable. The Secret Phrase is 132 bits of uniform randomness, so stretching it would add latency without adding security. Both halves meet at the XOR, and neither alone produces the unlock key.
Why two slowdowns rather than one
PBKDF2 is the approved floor and your browser implements it natively. Argon2id adds the memory-hardness PBKDF2 structurally cannot, which is what raises the cost of cracking a stolen database. Chaining one into the other cannot weaken either, and if the Argon2 stage were ever unavailable the password has still passed through a million rounds of an approved function.
Why the costs can rise later
Every cost in the tree is stored per account and handed to your browser before you sign in, so all of them can be raised for everyone without stranding existing accounts. An account adopts a new profile the next time its Master Password changes.
What the server stores
Every encrypted column in the database holds one envelope, and the envelope is the same four fields everywhere: the format version, the algorithm, the nonce that made this particular encryption unique, and the sealed bytes. The server validates the shape and the size. It cannot validate the contents, by design.
one envelope, as stored
{"v":2,"alg":"A256GCM","n":"Vf7Kd3wZs2…","c":"hQ2nZpVt7aXeR4mKdJ0sLbYc9wUf…"}
A256GCM is the only algorithm there is: with no key pair anywhere in the product, every wrap and every seal is symmetric. The version number exists so the format can evolve without ambiguity: a new value means a new format rather than a redefinition of this one.
what is inside an envelope, and what is not
item contentssealed
item titlessealed
vault namessealed
your wrapped keyssealed
email addressreadable — it is how you sign in
item count, timestampsreadable — the rows have to exist
The readable columns are structural: a row has to exist to be fetched, and it has to carry a modification time for sync to work. That is the full extent of the metadata this design leaves in the clear.
Post-quantum posture
A password manager has an unusual problem: anything copied out of it today can be kept for decades and opened once the tools catch up. The question is therefore not whether a quantum computer exists now, but whether anything stored here would fall to one later. Three reasons it would not:
Nothing to break in the first place
The quantum result that genuinely breaks encryption undoes one particular kind of maths — the kind behind RSA and elliptic curves, which is what protects most of the web today. Vault3 uses none of it, anywhere. Your account has no key pair, and nothing in our database was ever locked with one.
Sized for the shortcut that does exist
Against everything else, a quantum computer roughly halves the strength of a key — so every key here is sized to give that half away and still be far out of reach: AES-256 keeps 128 bits, SHA-512 keeps 256, your Secret Phrase keeps 66. Each is at or above what NIST treats as the post-quantum minimum.
Sharing adds no weak spot
The construction in section 4 keeps the key out of our hands entirely, and what we keep is a bundle only that key opens. So sharing adds nothing to the store that could be harvested now and opened later, which is where a service like this would otherwise acquire an expiry date.
This posture follows from how Vault3 is built rather than from a feature added on top. Nothing in it is due to be swapped out for a quantum-safe replacement later, so there is no future migration to get right.
Around the vault
The key hierarchy above is what keeps your vault closed. What follows are the layers around it, and each figure quotes what that layer actually produces.
Sessions
Signing in hands your browser one long random token, held in a cookie other sites and scripts cannot read. The server stores only a hash of it, so a stolen database yields no usable session. Every device is listed, any of them can be signed out, and changing your Master Password signs out all the others at once.
Two-factor authentication
You can add a code from an authenticator app at sign-in. The seed that produces those codes is encrypted in the database as well.
Operational data is encrypted too
A few things have to be readable by the service itself: your display name, the device and address a session came from, your alerts, the account history. These are encrypted with a separate key that the server holds, so data sitting outside your vault is also unreadable in the database. This is defence in depth for operational data. It is not part of the zero-knowledge property and should not be presented as such.
Alerts that always arrive
A new device signing in, the Master Password changing, two-factor going on or off: you are told as it happens. The emails can be turned off in Settings; the in-app alerts cannot, because silence is what an attacker using your account would want.
Auto-lock
While your vault is open, its keys sit in that browser tab and nowhere else. They are erased on lock, on sign-out and after a period of inactivity. Locking is local, so it takes effect immediately and works with no connection.
The connection itself
Everything travels over TLS, and your browser is instructed to refuse anything less. The vault may load code from this origin only: no content delivery network, no third-party script, and no permitted destination for an injected payload to send to. The sign-in and sign-up pages carry one narrow exception — Cloudflare's bot check, allowed to run there and nowhere else. Repeated sign-in attempts are rate-limited, and the responses are uniform, including on whether an email address is registered.
The Argon2id module
The memory-hard half of your key derivation is a WebAssembly module, because that work is what JavaScript handles worst: a pure-JavaScript version costs roughly ten times as much, and the only way to recover that would be to weaken the parameters on your device while an attacker's hardware pays nothing. It is compiled from Go, in this repository, wrapping the same library the server uses, so the browser and the server cannot disagree about your key.
Reproducible
The build pins the compiler version and strips every path and every trace of git state from the output, so the artifact is byte-identical on any machine. The digest can be regenerated independently rather than taken on faith.
Pinned
The app carries a SHA-512 of the module it expects. Your browser hashes what it actually received and refuses to run anything else. It fails closed rather than falling back to a weaker derivation, because a vault encrypted under a key its owner cannot reproduce is unrecoverable.
Verified against known answers
A digest only proves the bytes are the intended bytes. A test suite runs the compiled module against vectors produced by Go's own Argon2, including one at the exact production settings, which rules out a module that loads cleanly and derives the wrong key.
Check it yourself
Two commands in a clone of the repository: rebuild the module from source and compare, then run it against the reference vectors.
What the pin defends against is precise: transport and storage, not the server itself. A poisoned cache, a corrupted download or a stale artifact from a bad deploy are all caught. A malicious server is not, because the same server ships the pin, which is why the build is reproducible from public source.
Threat model
Who might come after your vault, and what each of them actually gets. The last two rows are the ones no password manager can dismiss, and they are printed here rather than omitted.
| Adversary | Outcome |
|---|---|
| Someone who steals the whole database | Sealed items, hashed authentication keys, hashed session tokens, encrypted operational fields. No vault contents, and no key that would open any. |
| Someone with a quantum computer | Nothing to break with Shor's algorithm, because no asymmetric primitive exists here. Grover's leaves at least 128 bits everywhere. Ciphertext harvested today stays unreadable. |
| A court order, or us acting in bad faith | Ciphertext and account metadata can be handed over, because that is all there is, and it cannot be decrypted. Poisoned JavaScript could be served to a future visit: the residual risk every web app carries, and the reason the code is public and the wasm reproducible. |
| Someone watching the network | TLS everywhere, HSTS, and cookies that never travel over plain HTTP in production. What crosses the wire is ciphertext and an authentication key that opens nothing. |
| Someone holding your locked device | Needs your Master Password, and your Secret Phrase too unless this device was set to remember it. The keys are wiped from the tab on lock, on sign-out and after inactivity. |
| Malware on your unlocked device | Out of scope. Software running as you, on your machine, while your vault is open can read what you have opened. No web vault survives that. |
What this does not cover
Unrecoverable is a property, not a policy
There is no reset endpoint and there will not be one: a credential-replacing route reachable from an inbox is a standing takeover surface for a product whose claim is that holding the server buys nothing. Losing both secrets means the ciphertext stays closed to everyone. Deleting the account and starting again is the only path out, and it is authenticated by the key you already hold.
The code arrives from us each visit
This is a web application, so the JavaScript doing the encrypting is served by the same people who hold the ciphertext. The mitigations are real — a strict policy that allows code from nobody else wherever your vault is open, and public source — but a browser extension or desktop client with pinned code would be stronger, and that is on the roadmap rather than shipped.
Unlocked keys live in the tab
While a vault is open its keys sit in that tab's session storage, and a device set to remember its Secret Phrase keeps that in local storage too. Cross-site scripting is the residual risk this accepts, held down by a policy that admits code from no other origin on every page where a vault is open. Anything already executing as you, in your browser, can use what you have unlocked, which is the boundary of every web vault, this one included.
One place a quantum adversary still has purchase
Not your vault, but the connection carrying it. A TLS session recorded today, whose key exchange was a classical one, could in principle be opened later. What that exposes is ciphertext and an authentication key, not vault contents, and we terminate TLS with a hybrid quantum-safe exchange where the infrastructure supports it.
Disagree with any of this?
A design document is worth more when somebody has tried to break it. If a claim above does not hold, or the implementation has drifted from what it says, tell us.
[email protected]Read it. Then check it.
The implementation is public, the wasm is reproducible, and the vault is free while we are in beta.
Create your vault