Skip to content

Security

vauly is built so that the server holding your data can’t read it. This page describes how, what the server can still see, and where the model stops protecting you. To report a vulnerability, see SECURITY.md.

  • Someone with the database, or full control of the API, learns no passwords, item names or item contents.
  • Your master password never leaves your device, so the server can’t leak it.
  • People only open the vaults that were shared with them, enforced by cryptography as well as by the API.
  • Scripts and agents get the values they’re allowed to use, inside the command they run, and nothing else.

All cryptography is WebCrypto, implemented once in packages/crypto and used by the web app and the CLI.

  1. Master passwordOnly in your head
    Never sent
    PBKDF2-SHA256 · 600,000 rounds
  2. Master keySplit in two by HKDF
    Never sent
    HKDF
  3. Auth keySigns you in
    A hash of ithashed with scrypt
  4. Wrap keyOpens your private key
    Never sent
    opens
  5. Your private keyECDH P-256
    Sealed private keysealed with the wrap key
    opens
  6. Vault keyAES-256-GCM, one per vault
    A wrapped copy per memberwrapped to each member
    opens
  7. Item keyAES-256-GCM, one per item
    Wrapped item keywrapped with the vault key
    encrypts
  8. Your itemName, username, password, notes
    CiphertextAES-256-GCM, fresh IV
The server holds only the right-hand column. Without your master password, none of it opens.
  • Signing in. The browser asks for your salt, derives the auth key and uses it as the password for the session. The server stores only a hash of the auth key (Better Auth’s default, scrypt). For an email it doesn’t know, the server returns a stable made-up salt, so the sign-in page doesn’t reveal who has an account.
  • Unlocking. The wrap key opens your private key. With it the browser unwraps the keys of your vaults, then the keys of their items. All of these stay in memory: reloading the page, choosing Lock now or staying idle (15 minutes by default, set per device) forgets them. The session cookie alone opens nothing.
  • Encryption. Every encrypted value is AES-256-GCM with a fresh random 12-byte IV, stored as base64(iv).base64(ciphertext).
  • Sharing. To give someone a vault, the browser wraps the vault key to their public key: an ephemeral ECDH P-256 key pair, HKDF-SHA256 bound to the recipient’s public key, then AES-256-GCM. Only their private key can open it.
  • Two-step login. Someone who turns it on must give a six-digit code (RFC 6238: SHA-1, 30 seconds) after their password checks out. The server checks the code, so it keeps the setup key; it accepts one step of clock drift either way and never the same step twice. Without a valid code, the session the password just created is deleted before it’s returned. Ten recovery codes, 50 random bits each, are shown once and stored as SHA-256 hashes; each works once. The web app and the browser extension send the code the same way, in an x-vauly-code header. Two-step login guards signing in only: it adds nothing to encryption, which still rests on the master password alone.
  • Changing the master password derives new keys from a fresh salt and seals the same private key with the new wrap key. The server swaps the auth hash and the sealed key in one batch and signs out your other sessions. Your key pair, and so your access to shared vaults, stays the same.
Encrypted (the server can’t read it) In plain text (the server can read it)
Item contents: name, username, password or value, website, one-time code secret, notes, tags, earlier passwords Email addresses, display names and roles
Vault names, descriptions and colors The workspace name
Your private key Which vaults exist and who can open each one
Vault keys and item keys (wrapped) Item IDs, which vault they’re in, ciphertext sizes, revision numbers, timestamps
Share links: the copy of the item they carry (the key is only in the link) Favorites, and items in Recently deleted
Share links: which item, who made them, when they expire and how often they were opened
Recovery codes for two-step login (as SHA-256 hashes) Two-step login setup keys, so the server can check codes
The activity log: who did what to which item ID, and when
Agent names, their public keys, a hash of their token secret, and the commands they ran
Public keys of people and agents

An agent’s key pair is created in the browser. Its private key goes into the token, which is shown once and never stored by vauly; the server keeps the public key and a SHA-256 hash of the token’s secret. When a person approves a request, their browser wraps that item’s key to the agent’s public key. vauly run decrypts the item on the machine where it runs, passes the value to the command in its environment, and masks the value in the command’s output. See Agents and scripts.

Anyone who holds an agent token can use whatever that agent has been allowed to use. Treat a token like a password, give each agent only the items it needs, and pause or delete agents you no longer use.

The extension signs in with the same auth key as the web app and keeps the session token in its own storage, sending it as a bearer token. The API accepts sign-ins from chrome-extension:// and moz-extension:// origins for this, since each install has its own ID. The unlocked private key is kept only in the extension’s session storage, which lives in memory, can’t be read by pages or content scripts, and is cleared when the browser closes or after 15 idle minutes. Filling runs a small script in the page only when you choose a login to fill. The fill menu and the save prompt shown on a site are iframes of the extension’s own page, which the site can’t read; they list only logins saved for that site and never receive a password. To offer saving new logins, a content script on each page reads the username and password of a sign-in form when it’s submitted and hands them to the extension, which keeps them in session storage for that tab for five minutes at most. Submissions on your vauly server’s own pages are dropped, so the master password is never held. See Browser extension.

A share link shows one item to someone outside the workspace. The sender’s browser copies the item’s username, password or value, website, notes and, only if asked, its one-time code secret, then encrypts the copy with a new AES-256-GCM key. The server stores the ciphertext under a random link ID. The key goes into the link’s #fragment, which browsers never send to a server, so the link ID alone opens nothing.

The recipient’s page asks the server for the ciphertext only after they choose Show the item, which keeps link previews in chat apps from using up a one-view link. The server counts each opening in the same statement that checks the link’s limits, refuses links that expired, were used up, or whose item is in Recently deleted, and records each opening in the activity log. Anyone who can open the item can see its links and turn them off.

A link is a copy: changing the item later doesn’t change what the link shows, and anyone holding the full link can open it until it stops working. Send it the way you’d send the password itself.

To show a brand’s logo next to an item, the browser loads an image from Brandfetch’s CDN for that brand’s domain. Only the registrable domain is used (hubspot.com for app.hubspot.com), never a subdomain, and never an IP address or a private network name such as .local, .internal or .corp. Items without a website get a logo only when their name matches a short list of well-known services. Brandfetch requires a Referer, so it also sees the address of your vauly, along with your IP address, like any image request. It learns nothing else about the item. Turn logos off per device in Settings → Website logos, or for a whole instance by deploying with VITE_BRANDFETCH_CLIENT_ID=off.

These are the things vauly does not protect against. Please read them before you rely on it.

  • The server delivers the app. vauly runs in the browser, and the browser loads its code from your instance. Someone who controls the web app’s hosting could ship code that captures your master password the next time you sign in. This holds for every web-based end-to-end encrypted app. Host vauly yourself, keep your Cloudflare account secure, and limit who can deploy to it.
  • Removing someone doesn’t take back what they’ve seen. Taking a person out of a vault deletes their copy of its key, so they can’t open it through vauly again. But they may have copied passwords, or the vault key, while they had access. Change the passwords that matter after someone leaves: the Overview lists the items they could see that haven’t changed since.
  • Nobody can reset a master password. It is the only key to your private key. If you forget it, your items can’t be recovered by you, an admin or the server. An admin can invite you again so you start over.
  • A compromised device is out of scope. Malware on your computer can read what you can read while vauly is unlocked.
  • One-time codes live next to passwords. Storing a TOTP secret in the same item as the password is convenient, but it means the item alone is enough to sign in.
  • Metadata isn’t hidden. See the table above: the server knows who is in the workspace, who can open which vault, and when items are used.
  • No independent audit yet. The design is small and the code is open, but it hasn’t been reviewed by a third party.