Security
Zero-knowledge by construction
The server — API route handlers and database — never has access to plaintext file contents, nor to the key material needed to decrypt them.
Key hierarchy
Each step wraps the next and binds an identifier as AEAD additional authenticated data, so a ciphertext or wrapped key cannot be silently moved onto another record.
- 1
Vault passphrase
User-chosen, never transmitted. Feeds PBKDF2-HMAC-SHA256 at 600,000 iterations with a random 128-bit salt.
- 2
Key Encryption Key (KEK)
Derived fresh on every unlock, held only in memory, never stored.
- 3
Master key
Random 256 bits, generated once at signup. Persisted server-side only in wrapped form.
- 4
Project data key
One random 256-bit key per project, wrapped by the master key with the project id bound as AAD.
- 5
File version ciphertext
Each file version is AES-256-GCM encrypted under its project key and leaves the browser as ciphertext.
Defense in depth: server-side storage encryption
The ciphertext your browser uploads is encrypted again before it is written to Postgres, under a 32-byte key that lives only in the deployment's environment and never in the database. A stolen database dump on its own is inert — it contains no plaintext and no usable keys.
Authentication & sessions
- Account passwords are hashed with Argon2id; they are never used directly as encryption keys.
- Access tokens are short-lived; refresh tokens are stored in an httpOnly, same-site cookie.
- Every device/session is listed and independently revocable.
Authorization
- Every project, file and version operation is authorization-checked on the server.
- Identifiers are non-guessable UUIDs; you cannot reach another account's data by changing an id.
- Frontend checks are never the only line of defense.
What we deliberately do not do
- We do not log plaintext environment variables, keys, tokens or passwords.
- We do not expose secret values in API responses or the web UI by default.
- We do not send secrets to third-party analytics or monitoring.
- We do not have a passphrase-recovery mechanism — there is nothing on our side to recover from.
Transport
All traffic is HTTPS/TLS with HSTS. A strict Content Security Policy blocks external origins; the app loads no third-party scripts.