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. 1

    Vault passphrase

    User-chosen, never transmitted. Feeds PBKDF2-HMAC-SHA256 at 600,000 iterations with a random 128-bit salt.

  2. 2

    Key Encryption Key (KEK)

    Derived fresh on every unlock, held only in memory, never stored.

  3. 3

    Master key

    Random 256 bits, generated once at signup. Persisted server-side only in wrapped form.

  4. 4

    Project data key

    One random 256-bit key per project, wrapped by the master key with the project id bound as AAD.

  5. 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.

Your responsibility: because only you hold the vault passphrase, losing it means losing access to the encrypted data. Keep it in a password manager and back up critical files offline.