A passkey-only, post-quantum, end-to-end encrypted password vault. No server. No domain. No account. No master password.
Chrome / Edge extension only. Everything runs locally.
日本語は README.ja.md を参照。
This is an experimental project by one developer. It has not been audited by anyone.
- The cryptographic core has 149 tests, including the full recovery path.
- The end-to-end recovery flow has been verified once on real hardware (one PC, one authenticator).
- It has not been tested across multiple devices, multiple authenticator vendors, or over time.
- No third party has reviewed the design or the code.
Do not make this the only place your passwords exist. Keep your current password manager until you have personally completed the recovery drill below and are satisfied.
The most common way people lose a password vault is not an attacker breaking in — it is losing access to their own data. This design deliberately has no server and no company that can help you, which removes an entire class of attacks and also removes any safety net. That trade is the whole point, and you should make it knowingly.
Most password managers protect your vault with a master password. That means a stolen vault file can be attacked offline, forever, at the attacker's leisure. Raising the KDF iteration count only buys time.
YOURVAULT has no password to attack. The vault key comes from your passkey's WebAuthn PRF extension — a 32-byte HMAC-SHA-256 output that lives inside your authenticator's secure element. There is nothing to guess.
Vendors are loose with this word, so here is the precise scope:
| Layer | Algorithm | Post-quantum? |
|---|---|---|
| Vault contents at rest | XChaCha20-Poly1305 (256-bit) | ✅ ~128-bit under Grover. No migration needed. |
| Key derivation | HMAC-SHA-256 (in authenticator) → HKDF-SHA-512 | ✅ Symmetric only. |
| Recovery kit | 256-bit CSPRNG key | ✅ Symmetric only. |
| Vault key transport / rotation | X-Wing (X25519 + ML-KEM-768) — IETF CFRG draft; ML-KEM-768 per FIPS 203 | ✅ Hybrid. |
| Vault manifest signing | Ed25519 + ML-DSA-65 — FIPS 186-5 and FIPS 204 | ✅ Hybrid, both must verify. |
| Passkey signature (login) | ES256 / RS256 / EdDSA | ❌ Not post-quantum. |
Those spec numbers name the algorithms, not a certification. The implementation is @noble and is not FIPS-validated — no CMVP certificate stands behind any of it. X-Wing is a CFRG draft, not a FIPS standard; FIPS 203 covers ML-KEM on its own. An earlier version of this table said "X-Wing, FIPS 203" and "Ed25519 + ML-DSA-65, FIPS 204", which read as a certification claim. It was not one.
That last row is not a choice we made — no shipping authenticator supports ML-DSA passkeys today. It is also not a confidentiality problem: a signature carries no secret, so "harvest now, decrypt later" does not apply. An adversary with a quantum computer could forge an assertion, but they still cannot derive the PRF output, so the vault stays closed.
We will never claim "fully post-quantum". The vault contents are. The login signature is not, and neither is anyone else's.
- Nothing to brute force. No master password anywhere in the system.
- One cipher suite, no legacy modes. Downgrade attacks have nothing to target.
- Every ciphertext carries AEAD associated data binding it to the account and to its
purpose, so cut-and-paste and field-swapping fail. Item ciphertexts additionally bind
the item ID, item version and key epoch. (The two KEK-layer envelopes inside an unlock
slot deliberately leave the key epoch out, so that rotating the vault key needs no
authenticator touch — see the comment block in
core/src/envelope.js.) - The whole vault is one opaque blob padded to 64 KiB steps. A backup file does not reveal which items changed, and it hides the exact item count — measured, an empty vault and one holding ~100 ordinary logins produce byte-identical file sizes. It does still leak a coarse size band (crossing a 64 KiB step is visible), because no padding scheme can hide unbounded size, and one very long note can push a small vault into the next step on its own.
- Rollback detection. Monotonic counters plus a hybrid signature; an older vault is refused before any key touches it.
- Two factors, not one. FIDO's Credential Exchange Format exports
hmac-secretseeds in the clear, so a PRF output is not hardware-bound. YOURVAULT mixes in a device-local secret, so compromising your passkey provider is not enough. - Revocation rotates the key, not just a record. Removing a passkey mints a new vault key and re-encrypts everything; deleting a record alone would be theatre. Known limit: this does not reliably evict an attacker who already held the vault key — see Known limitations.
- Autofill is never automatic. Selection happens in extension-owned UI, never in page-rendered DOM, and only on exact origin match. This is a direct response to the 2025 DEF CON research where all 11 tested password managers were clickjackable.
Full rationale, including the threat model and what it explicitly does not protect against, is in DESIGN.md (Japanese).
There is no server, so your backup file is the vault.
If you lose your backup file, your recovery key alone will not bring your data back.
The recovery key opens the envelopes; the file is the envelopes and the contents. The UI warns you every time the backup goes stale. Keep the printed key and the backup file in different places, and keep the file in at least two.
Backup files contain no plaintext secrets — safe to put in Drive, Dropbox, S3, a USB stick, anywhere. The envelope around them is readable JSON, though, so a backup does reveal how many passkeys the vault has, when each was enrolled, their WebAuthn credential IDs, and any device label you typed. Don't put personal details in a device name.
Requires Chrome or Edge 147+. Node 20+ to build.
git clone https://github.com/baadee55/yourvault.git
cd yourvault
npm install
node scripts/fix-extension-id.mjs # generates your own extension key (offline, no Google account)
npm run build
npm test # 149 testsThen chrome://extensions → enable Developer mode → Load unpacked → select the extension/ folder.
The WebAuthn RP ID is this extension's ID. Without a fixed key in the manifest,
Chrome derives the ID from the folder path — so moving the folder would destroy every
passkey. The script generates an RSA keypair locally and pins the ID. No Chrome Web
Store account is needed.
The generated key is .gitignored on purpose: if it were committed, every user would
share one extension ID. Copy both generated files to any other PC where you want to
open the same vault — the IDs must match.
- Click "Check if this device works". It creates a throwaway passkey and measures
whether PRF actually comes back. Do not skip this: Chrome's
getClientCapabilities()reportsextension:prfas hardcodedtrue, so it tells you nothing. - Create a new vault. Print the recovery kit and save the backup file — the dialog will not let you continue until you confirm both.
- Do the recovery drill before entering anything real: add a dummy item → save a backup → delete every passkey from your OS → Restore from backup → Full recovery → confirm the item comes back.
- Bitwarden password-protected JSON — recommended. Nothing is ever written to disk in cleartext.
- Bitwarden plain JSON / CSV.
- CSV from anything else — 1Password, LastPass, KeePass, Chrome, Edge, Firefox, Dashlane, NordPass, Proton Pass, Apple Passwords, and unknown products too. Columns are matched by meaning, not by a per-vendor parser, so it does not break when a vendor changes their export.
If a CSV has no password column, the import fails loudly instead of reporting "0 items imported" — you should never delete your export because of a silent no-op.
- Backup (
.lvault) — fully encrypted, no extra password. Adding a password would reintroduce the exact low-entropy, brute-forceable secret this design removes, and it would become the weakest link. So we don't. - Cleartext JSON / CSV — for migrating away. Loud warnings, embedded in the file itself.
- Revoking a passkey does not evict an attacker who already had the vault key. The keyset has no authenticity anchor that a vault-key holder cannot produce. Someone who reached the vault key once — an unlocked, compromised device — can either swap their own KEM public key into a surviving unlock slot, or simply add a slot of their own. Either way the next rotation re-wraps the new vault key for them, so revoking a passkey hands the attacker the replacement instead of locking them out. Rotation is deliberately possible without touching your authenticator, so no secret exists at rotation time that could tell a real slot from a planted one. Both variants are reproduced in the test suite. What the code does now is make them loud rather than silent: a swapped public key fails with a distinct "this slot may have been tampered with" error the next time its owner unlocks, and importing a backup that contains an unlock path this device does not already know about requires an explicit confirmation. If a device was compromised while the vault was unlocked, do a full recovery (which mints an entirely new key set and a new recovery kit) — revoking is not enough.
- Passkey signatures are not post-quantum (see above). Confidentiality is.
- The device secret is not hardware-sealed. It is protected by a non-extractable
CryptoKey, which resists reading the profile directory, but not arbitrary code running in the same browser profile. A browser extension cannot do better. - Sync is manual — you carry the backup file.
- Argon2id takes ~4–5s in pure JS (measured 4.7s at t=3, m=64 MiB). It runs on every path that touches the recovery key: creating a vault, recovering (twice — once to open the old kit, once to mint the new one), reissuing the kit, and enrolling a device with the key. Importing a Bitwarden export that uses the Argon2 KDF also runs it once. It is defence-in-depth, not load-bearing (the recovery key is already 256 bits).
- iOS / Safari are out of scope. Apple does not pass WebAuthn extension data to external security keys, and third-party credential providers cannot serve PRF.
core/ Crypto core — pure JS, no DOM, runs in Node and the browser
src/format.js wire format, AAD construction, algorithm IDs
src/envelope.js key hierarchy, unlock slots, rotation
src/vault.js vault model, rollback detection
src/archive.js backup / export format
src/import/ Bitwarden + generic CSV importers
test/ 149 tests
extension/ Chrome MV3 extension (the only client)
scripts/ build + extension-ID pinning
Issues and PRs welcome. If you find a security problem, please open an issue — this project has no users to endanger yet, and public discussion is more useful than a private disclosure process I cannot staff.
Independent review of the cryptographic design is the single most valuable thing anyone could contribute. I wrote it, so I cannot audit it.
MIT — see LICENSE.