All work

Cubby

A zero-knowledge password manager

  • Server only ever holds ciphertext
  • Shared vaults without breaking the model
  • Proven by test, not by claim

Cubby is a self-hosted password manager — a Bitwarden replacement with a web dashboard and a Firefox extension. The constraint that shapes every decision in it: the server never sees anything it could decrypt. Your master password and vault keys do not leave the client.

That is easy to claim and easy to get subtly wrong, so it is asserted by a test that talks to a live Postgres, registers a user, creates an item, and fails if any row holds anything but ciphertext. The property is enforced by CI rather than by my good intentions.

Where the crypto lives

Key derivation happens in the browser. The symmetric key stays in memory only — reload the page and you get the unlock screen back, master password required. The API deals almost exclusively in opaque blobs; it can tell you that an item changed and when, never what it says.

The unlock screen. Reload the tab and you are back here, because the key was only ever in memory.

The vault. Names, usernames and TOTP codes are all decrypted client-side; the rows they came from hold nothing but ciphertext.

An item open, with the password revealed and a live TOTP code counting down beside it.

Sharing without cheating

Shared vaults are where zero-knowledge designs usually quietly compromise. Cubby gives every user an RSA key-pair, with the private key wrapped under their own symmetric key. A shared vault has its own key, delivered to each member encrypted to that member's public key. Invite by email, accept, and everyone can read and edit — while the server still holds nothing but ciphertext and a set of per-member wrapped keys it cannot open.

Inviting someone to a shared vault. Accepting is what delivers them the vault key, encrypted to their public key — the server never handles an openable copy.

The rest of it

Revision-based delta sync against an encrypted local cache, so a client fetches what changed rather than the whole vault. Folders with encrypted names. Live TOTP codes. A configurable generator with look-alike exclusion. Client-side Bitwarden JSON import — decrypted, re-encrypted and uploaded without the plaintext touching the wire. Account recovery via a one-time key issued at signup, because "we can reset it for you" is precisely what a zero-knowledge system cannot offer.

The generator. Look-alike exclusion is the toggle that stops a password containing characters you cannot tell apart when reading it aloud.

Password health runs entirely on the client, because it has to — the analysis needs the plaintext, and the plaintext only exists in the browser. Weak and reused passwords are found locally; the breach check sends a five-character hash prefix and matches the rest here.

Password health. Reuse is exact-match across the vault; the breach check is HIBP k-anonymity, so no password or full hash leaves the client.

Running it

One docker compose up brings up Postgres, the API, and a Caddy proxy handling auto-TLS and routing, plus a scheduled pg_dump backup with retention. The Firefox extension is built with WXT and is the piece still in progress.