Security

Clane Vault security model

What is encrypted, where and with which keys, what the server can see, what a compromised server could still do, and what hasn't been checked by an outside firm yet.

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.

What the server knows about a file
The server knowsThe server doesn't know
That a record has files, each one's id and ciphertext size, who added it and whenFile names, types, content or the hash of the content
Who downloaded which file and whenWhether two files are the same (each has its own key)
Storage used per organisation, person and serverAnything inside the record

Sharing and organisations

A shared folder has its own random key. That key is sealed to each member's membership key with an X25519 sealed box: an ephemeral X25519 key exchange, HKDF, then AES-256-GCM. A record shared directly is handled the same way, with its record key sealed to the recipient. The server passes these sealed keys along but can't open them.

Access levels are Owner, Can edit, Can view and Use only. Use only hides a password in the app but still lets the person copy it, so for passwords it is a soft limit. For files it is a hard limit: the server refuses to send file contents to Use only holders.

Each person's key has a fingerprint, shown on the People page, in the share and member pickers and on each folder's people sheet. The app also warns you if a colleague's key has changed since you last gave them access.

When someone is removed from a folder or a share, their access rows are deleted in one transaction, the records they owned there pass to the folder owner, and the one-time links they made on those records are revoked. The folder and record keys are not rotated, so someone who kept a copy of a key could still decrypt ciphertext they already downloaded. The app suggests changing important passwords after a removal.

Each organisation has its own keys. Its admins can't decrypt another organisation's items or anyone's personal vault.

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.io when 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.