Summary
Vault items and files are encrypted in the browser before they're sent. The server stores their contents only as ciphertext, so a copy of the database holds your passwords only in encrypted form. These are the eight controls the rest of this page explains.
Two-step sign-in for every account
An authenticator code is required at sign-in, or your Entra ID or Google two-step check if your organisation trusts it. Each code works once, and 10 failed attempts lock the account for 15 minutes.
The master password stays on the device
It is stretched with Argon2id in the browser. The server never receives it, and holds the keys it unlocks only in encrypted form.
A separate key for every item and file
Each is encrypted with AES-256-GCM before it leaves the browser.
Sharing the server can't read
Keys are sealed between browsers with X25519 sealed boxes. You're warned if a colleague's key has changed since you last gave them access.
Organisations kept apart
Each organisation has its own keys. Its admins can't decrypt another organisation's items or anyone's personal vault.
Two ways to recover a lost password
Each person gets a recovery kit. Admin recovery is off by default, needs two admins (plus a 24-hour wait after a 2FA reset) and covers one organisation, never the personal vault.
Links that open once
The key sits in the link's # fragment, which the browser never sends, so the server can't open it. Add a password if you want.
Backups only you can read
Encrypted with your age key before they leave the container, then written to S3 or a folder.
Sign-in and two-step verification
Every account sets up an authenticator app (TOTP) when it is created, and a code is asked for at each sign-in. The server remembers the last 30-second step used on each account, so a code that has been accepted once is refused if it is replayed. The authenticator secrets are encrypted in the database with a key derived from the server's SERVER_SECRET.
Wrong passwords, wrong authenticator codes and wrong recovery codes all count towards the same lockout: after 10 failures the account is locked for 15 minutes. Requests are also rate limited per client address.
Organisations can add single sign-on through OpenID Connect, for example Microsoft Entra ID or Google Workspace. The identity provider confirms who the person is; the master password still unlocks the vault afterwards, so the identity provider never holds vault keys. An owner can choose to trust the provider's own two-step check in place of the authenticator code. It applies only to members who belong to that organisation alone and sign in with its verified domain, and the session stays limited to that organisation until the person enters an authenticator code.
The vault locks itself after 15 idle minutes unless the organisation sets a different time (1 to 1,440 minutes).
Master password and keys
The master password is stretched in the browser with Argon2id (64 MiB of memory, 3 iterations, parallelism 1, and a random salt per account). Two keys are derived from the result with HKDF:
- an authentication key, which is sent to the server to prove the password. The server stores only an Argon2id hash of it.
- a wrapping key, which never leaves the device. It decrypts the account's 32-byte user key.
The user key in turn decrypts the account's X25519 private key and its record keys. Each person also has one X25519 key pair per organisation they belong to (a membership key), so that keys shared inside one organisation can't be used to reach another.
The server stores these keys only in wrapped form. It doesn't receive the master password or the recovery code that would unwrap them.
Vault items and files
Items
Each record gets its own random 256-bit key. The record's fields, including its title, notes, custom fields and its record type, are encrypted with AES-256-GCM under that key, with the record's id bound in as additional data so the server can't move ciphertext from one record to another. Of the record type, the server sees only whether a record is a "login" (which the browser extension may fill) or an "item".
The installable app can keep an encrypted offline copy of the vault. Organisations can turn offline copies and the browser extension off by policy.
File attachments
Files up to 100 MB can be attached to any record. Each file gets its own random 256-bit key and is cut into 1 MiB chunks. Every chunk is encrypted with AES-256-GCM, with the file id, the chunk's position and the number of chunks bound in, so chunks can't be swapped, reordered, dropped or replayed. The file key and the file's name, type, size and SHA-256 are encrypted under the record's key. After a download the app checks every chunk and the hash of the whole file, and discards a file that fails.
Previews never render a file as HTML or SVG, never open it in a frame or new tab, and check an image's or PDF's real format before showing it. PDFs are drawn on a canvas by pdf.js with scripts off. Downloads are always handed to the browser as application/octet-stream, and programs such as .exe or .ps1 files ask before they download and say who added them.
Because the server holds only the files' ciphertext, it can't scan them for malware. Every file shows who added it, and the event log records each upload and download.
| The server knows | The server doesn't know |
|---|---|
| That a record has files, each one's id and ciphertext size, who added it and when | File names, types, content or the hash of the content |
| Who downloaded which file and when | Whether two files are the same (each has its own key) |
| Storage used per organisation, person and server | Anything inside the record |
One-time links
A one-time link sends a copy of a record to someone outside the vault, such as a client. The browser encrypts the copy under a random 32-byte link key and puts that key after the # in the link. Browsers don't send that part of an address to the server, so the server stores the copy but can't open it. The copy never includes a two-step (TOTP) secret.
- The person who makes the link chooses whether it opens once (the default), up to 3 times or until it expires, and whether it expires after 1 hour, 24 hours or 7 days. The last allowed open deletes the stored copy.
- A password can be added. The recipient's browser derives the key from the password with Argon2id together with the link key, and the server checks a hash before it releases the ciphertext.
- A link can carry up to 25 of the record's files (100 MB in total), chosen one by one. The recipient then has 30 minutes to download them.
- Only people who can edit the record can make a link. Links are revoked when their creator loses edit access, when the record is trashed, or when a file the link carries is removed.
Organisations can turn links off, set the longest expiry, require a password, and keep files out of links. Switching links off revokes the organisation's live links.
Recovering a lost master password
Recovery kit
After sign-up each person saves a recovery code made from 32 random bytes, with a checksum so typing errors are caught on the device. The server stores the user key wrapped under a key derived from that code, and a hash of a separate value used to check the code. It can't use the kit itself. Using the kit also needs the person's authenticator code. A used kit is deleted, and the person saves a new one before the vault opens.
Admin recovery
Admin recovery is off by default and is a setting of each organisation. When it is on, admins can help a member set a new master password and restore what the member holds in that organisation: its shared folders and the records shared with them there. They can never restore or open the member's private items or anything in another organisation. That also means admins can read what the member holds in that organisation.
- Turning it on needs the admin's master password and a fresh authenticator code. Members are emailed, are told on sign-up and in Settings, and must confirm a notice before their key is escrowed.
- Approving a reset needs the admin's fresh authenticator code. The member finishes it with a link that works once and for 24 hours, plus their own authenticator code. The admin never sees or sets the new password.
- If the member's two-step sign-in was also reset, the admin who reset it can't approve, the approving admin must have been an admin for at least 7 days before the reset, and the member can't set up a new authenticator until 24 hours after the reset. The member is emailed at each step, and every admin is emailed when a new admin account appears.
- Turning admin recovery off deletes every escrowed key and ends open requests.
Two risks are left. A stolen recovery kit plus a two-step reset obtained by deceiving an admin is an account takeover. So is an honest two-step reset approved by a long-standing admin acting in bad faith, if the member ignores the emails. Admins should confirm who they are talking to, by phone or in person, before resetting anyone's two-step sign-in and before approving a reset. Every recovery step is in the event log.
Backups
Scheduled backups are built in and off until you turn them on. Each run dumps the app's database schema with pg_dump, encrypts the dump inside the container with age to your public key, and only then writes it to S3-compatible storage or a mounted folder. The app refuses to start with backups on and no age recipient set, so it never writes an unencrypted backup.
Keep the age private key offline or in your secrets manager, never on the vault server.
Back up the .env file separately. Its SERVER_SECRET encrypts everyone's authenticator secrets: if it is lost, every account that set up an authenticator needs a two-step reset by an admin. Vault data isn't affected, because it is encrypted with each person's own keys. Since 1.6.1 the server refuses to start if SERVER_SECRET differs from the one the database was set up with.
What is stored in plain text
Some data has to be readable by the server for sign-in, invitations and auditing. It is stored unencrypted in your Postgres:
- member email addresses and display names
- organisation and group names
- record categories ("login" or "item"), and which records and folders each person can open
- for files: how many a record has, their ciphertext size, who added them and when
- the event log: who did what and when, including who was given access to what and who downloaded which file. It never contains a password, a file name or a file's hash.
Treat database dumps as sensitive even though they contain no readable passwords.
If the server is compromised
The server can't decrypt vault items or files. A server that has been taken over could still do harm, though:
- It could serve malicious JavaScript that reads passwords as people type them. Encryption in the browser can't protect against this. The app sends a Content Security Policy that allows scripts only from its own origin, and when you self-host, an attacker has to get into your infrastructure to change the code.
- It could swap public keys, so that a new share is sealed to the attacker. Existing shares aren't affected. Compare key fingerprints with the colleague by another channel before sharing anything sensitive.
- It could plant fake shared folders or records, because sealed boxes aren't signed. The folder sheet shows the owner's name and key fingerprint. Don't type secrets into a folder whose owner you haven't checked.
- It could hide records or serve an older version, because the server decides which rows to return.
Run the image you were given, keep the host patched, and limit network access to Postgres to the container host.
What leaves your network
When you self-host, no data reaches Clane. There is no telemetry and no licence server. Traffic leaves your network only for:
- the breached-password check: the first 5 characters of a SHA-1 hash go to Have I Been Pwned. Members opt in, and admins can switch it off.
- sign-in with Entra ID or Google, if you set up single sign-on
- your mail provider, for invitations and security notices
- your S3 storage, for backups and, if you choose, file attachments
- image downloads from
ghcr.iowhen you install or update
Audit status
No external audit yet
No independent firm has audited or penetration-tested Clane Vault yet. An external audit is planned, and we will publish the results.
The code has been through internal security reviews, covering multiple organisations, email and single sign-on, per-organisation keys and file attachments. The fixes are listed in the changelog. The full source is on GitHub under AGPL-3.0, so your own team can review it.
To report a security problem, email [email protected] or use private vulnerability reporting on GitHub.