“Zero-knowledge” has become a marketing word. Plenty of services print it next
to a padlock icon while holding a copy of the key that opens your data — sometimes
in a hardware module, sometimes just in a column called user_key. The term is
only useful if it comes with a testable claim, so here is ours.
If an attacker obtained our entire database, every disk in every storage node, and the private keys of every server we run — they would still not be able to read a single file, filename, or folder name.
That is the bar. Everything below is what it forces us to give up in order to meet it, and the places where it genuinely does not protect you.
The test: what a full breach reveals
The honest way to describe a system's privacy is to describe its worst day. Imagine an attacker walks away with everything we store — the full database, every disk in every storage node, every server key. What do they hold?
| They get | They don’t get |
|---|---|
| Encrypted chunks of ciphertext | The contents of any file |
| Encrypted filename and metadata blobs | What anything is called |
| Chunk sizes, content types, timestamps, and which account each chunk belongs to | Your password or private keys |
| Account email addresses and billing records | A way to decrypt any of it, short of guessing your password |
| The social graph: who shared with whom | What was shared |
The right-hand column is the product. The left-hand column is the honest cost, and we will come back to it — because pretending it is empty is exactly the dishonesty we are trying to avoid. One qualifier belongs in the open, too: the last line holds because the only remaining attack on a stolen database is guessing your password offline, and while Argon2id makes every guess expensive in memory as well as time, no function can rescue a password that is easy to guess. That one choice stays in your hands.
Why we never see your password
Most services take your password over TLS, hash it on the server, and store the hash. That is fine for authentication and useless for encryption: at the moment you log in, the server briefly holds your actual password in memory. A compromised server can log it.
Your password never reaches us in a usable form. Before anything leaves the browser, it is hashed client-side and only that digest is sent over the wire — the server then treats the digest as an opaque secret and runs it through Argon2id before storing it. The server can verify you are you. It cannot reconstruct what you typed.
That digest has a second job — and this is the part that matters. Combined with a random per-account salt, it is fed through Argon2id in your browser to derive the key that unlocks your seed vault: the encrypted bundle holding your Ed25519 signing seed — from which your X25519 key-agreement key is derived — and your ML-KEM-768 post-quantum key. That derivation happens in your browser and the resulting key never leaves it. And because what we store at rest is only a one-way Argon2id hash of the digest, a copy of our database contains nothing that can be run backwards into the key that opens your vault.
The practical consequence: if you forget your password, we cannot recover your files. Not “we won't” for policy reasons — we hold nothing that could. Any provider offering both zero-knowledge encryption and password recovery is doing one of the two things loosely.
Why file bytes never touch our API
A subtler failure mode: a service can encrypt correctly and still funnel every byte through one server that could, in principle, be modified to keep a copy before it re-encrypts.
Our control-plane API never receives file content at all. It handles accounts, buckets, permissions and billing, and when you want to upload it issues a signed placement plan — effectively a short-lived, cryptographically signed note saying “this client may write these chunks to these nodes.” Your browser then encrypts each chunk and uploads it directly to the storage nodes.
The storage node checks the signature on the token and writes the opaque blob to disk, together with a small plaintext recovery record naming the owning account, bucket and object. So a node can tell whose chunks it holds and which of them belong together — that is what lets replication and disaster recovery stay mechanical — but it holds no key material of any kind and can never read what is inside them. It knows whose and which, never what. The full pipeline is described in how your files are encrypted before they leave the browser.
What zero-knowledge does not protect you from
This is the section most pages skip. Encryption is a narrow tool and it is worth being precise about its edges.
Metadata is not content, but it is not nothing
We can see that an account uploaded roughly 4 GB last Tuesday and shared something with three other accounts. We cannot see what. For most people that is an acceptable trade; if your threat model includes traffic analysis by someone who already has our servers, it may not be.
We deliver the code that does the encrypting
Browser-based end-to-end encryption has an unavoidable structural weakness: you fetch the JavaScript from us each visit, so a coerced or compromised provider could serve a modified build to a targeted user. No web application escapes this. It is why our desktop SDK exists as a pinned, inspectable artifact, and why the browser crypto is a byte-for-byte port of it rather than a separate implementation — the two can be diffed against each other.
Your device is still your device
Plaintext exists on your machine, because that is the only place it can. Malware, a shoulder-surfer, or an unlocked laptop defeats all of this. Encryption moves the weak point; it does not remove it.
A share is a decision you cannot take back
When you share a file, you hand someone the ability to decrypt it. Revoking access stops future reads — it does not un-read what they already downloaded. This is true everywhere, but zero-knowledge makes it starker: we cannot reach into their copy.
Claims should be checkable
The uncomfortable thing about every statement above is that we are the ones making it. So we try, where possible, to make the claims verifiable rather than asking for trust:
- The client crypto is open to inspection, and the browser and SDK implementations are deliberately identical so they can be compared.
- Our audit log is hash-chained with BLAKE3, its Merkle roots are published with inclusion proofs, and those roots are anchored to Bitcoin via OpenTimestamps — see why we publish a tamper-evident audit log.
- The audit verifier ships as a separate program that recomputes the published Merkle roots and proofs itself instead of trusting the values our API reports.
None of that makes us trustworthy by decree. It just means that if we ever stopped being trustworthy, you would have a way to find out.
Next: a walk through the upload pipeline, from Argon2id key derivation to the moment ciphertext lands on a storage node.