Security

Trust, demonstrated.

Manifa is end-to-end encrypted by construction, not by policy. Encryption happens on your machines; the server is a blind store. Here is exactly how — and where the honest limits are.

What the server never sees.

Everything content-bearing is ciphertext before it leaves your device. There is no server-side key that can reverse it.

File contents
File and directory names
Your .env values
Account password or recovery code
Any encryption key, ever

What it does see.

We won't pretend metadata doesn't exist. The server learns a little — and we pad chunk sizes to blur even that.

That a vault exists
The number and size of encrypted chunks
How many devices you have
When a sync happened
On your machine
  • DATABASE_URL=postgres://…
  • STRIPE_KEY=sk_live_…
  • app.tsx
  • schema.ts
What the server stores
  • a3f1c08e…
  • 9b27dd4a…
  • 1f0e6c52…
  • c84b91ff…
  • 70a2e3d9…
Key hierarchy

A root key
only you hold.

At signup your device generates a 256-bit Account Master Key. It never leaves your machine in plaintext. The server only stores wrapped copies of it — sealed by a key derived from your password, by your recovery code, and by each device's public key.

Every Vault Key is wrapped by the master key. Per-chunk Chunk Keys are derived, never stored. It's the same model that backs Bitwarden and 1Password.

Account Master Key

256-bit · never leaves your device

Vault Key

one per vault · wrapped by AMK

Device wraps

AMK sealed to each device key

Chunk Key

derived, never stored

The primitives

Boring crypto, on purpose.

No home-rolled ciphers. Standard, well-reviewed primitives composed carefully.

AES-256-GCM-SIV

Authenticated content encryption, nonce-misuse-resistant — the correct choice for the deterministic nonces convergent encryption requires.

Convergent, within a vault

A chunk key is HMAC(vault key, plaintext hash), so identical content dedupes — but only inside your own vault, so the cross-user confirmation weakness never applies.

XChaCha20-Poly1305 wrapping

Key-wrapping (master-to-device, vault-to-master) uses XChaCha20-Poly1305 with random nonces. Argon2id derives the wrap key from your password.

BLAKE3 content addressing

Every chunk is addressed by BLAKE3 of its ciphertext. The store is keyed by content hash; the server sees opaque IDs and bytes, never paths.

Threat model

Who we defend against.

Honest-but-curious server or cloud provider

Learns nothing about file contents, names, or secret values. Sees only metadata it cannot decrypt.

Network attacker

TLS everywhere; payloads are already end-to-end encrypted underneath the transport.

Lost or stolen device

Revoke it at the device level. Revocation deletes its wrapped keys and rotates the affected vault keys.

What we won't claim

The honest limits.

Metadata leaks. The server learns chunk sizes and sync timing. We bucket and pad chunk sizes to blur it, but we don't claim it's zero.

True recovery means a recovery code. Because we hold no key, losing every device and your recovery code means the data is gone. We make backing it up unmissable at signup, but the math is the math.

A malicious server is out of scope for v1. Authenticated encryption plus Merkle-root verification detect forged ciphertext, but we don't claim Byzantine resistance to a server that actively serves bad data.

Encryption you can verify.

Dump our database and our object store and you'll find nothing but ciphertext. That's the bar we hold ourselves to.