The Zero-Knowledge Encryption Protocol
What happens to your secret, step by step.
Client-side encryption
Your secret gets encrypted on your own machine before it goes anywhere. Your browser does it with XChaCha20-Poly1305 through libsodium.
XChaCha20-Poly1305 is an AEAD cipher (authenticated encryption with associated data). It encrypts and authenticates in a single operation. The "XChaCha20" part is the encryption. The "Poly1305" part is the authentication tag that detects tampering. If anyone modifies the ciphertext in storage, decryption fails instead of producing garbage.
For each secret, your browser generates a fresh 256-bit random key using libsodium's secure random number generator. That key is never reused. A fresh 24-byte nonce is also generated per secret. With XChaCha20's 24-byte nonce, the probability of a collision is negligible even across billions of secrets.
Key derivation
The random key doesn't go directly into XChaCha20. First, your browser runs it through Argon2id, a memory-hard key derivation function designed to resist GPU and ASIC brute-force attacks.
Argon2id takes the random key, a fresh 16-byte salt, and produces the actual encryption key. The salt and the encrypted output are bundled together so the recipient's browser can reverse the process.
Even if the raw key material were somehow weak, Argon2id makes brute-forcing the derived key computationally expensive. Defence in depth.
Domain binding
The encryption includes one more input: the current domain (window.location.origin) as additional authenticated data. This binds the ciphertext to secret.broker specifically.
If someone copied the encrypted payload to a phishing domain and tried to serve it there, decryption would fail. The ciphertext only opens on the domain where it was created.
The payload format
What your browser sends to the server is a single binary blob:
[1 byte version] [16 bytes salt] [24 bytes nonce] [ciphertext]
The version byte is currently 1. If the cipher or parameters need to change in the future (for example, to post-quantum algorithms), the version increments. Old links still decrypt correctly because the recipient's browser reads the version byte and uses the right algorithm.
Zero-knowledge transport
The decryption key is in the URL fragment, the part
after the #. Your browser never sends the
fragment to the server. That's not a policy I set. It's how the HTTP protocol works.
When someone opens the link, their browser requests the encrypted payload from the server using the secret ID (everything before the #). The server returns the ciphertext. The browser then reads the key from the fragment and decrypts locally. The server serves encrypted data and never sees the key that opens it.
The agreement covers what this means in practice.
Ephemeral storage
Encrypted data is stored until the view limit or expiry is reached, then hard-deleted.
Small secrets (under 32 KB of ciphertext) are stored inline in the database. Larger payloads, like file attachments, are uploaded directly from your browser to object storage using a pre-signed URL. The server issues the upload URL but never handles the ciphertext itself for large payloads.
When a secret is created, an expiry message is queued with a delay matching the TTL you chose. When that message fires, the ciphertext is overwritten with an empty string and the record is marked destroyed. For large payloads, the object in storage is also deleted. This is a hard delete, not a soft delete or a tombstone.
The same hard delete happens when the view limit is reached. Every time someone opens the link, the access counter increments. When it hits the quota, the ciphertext is destroyed immediately.
File attachments
Files go through the same encryption as text. Your browser bundles the secret text and any attached files into a ZIP archive, then encrypts the whole archive with XChaCha20-Poly1305 using the same key and the same process described above.
On the other end, the recipient's browser decrypts the archive and unzips it. The server stores and serves the encrypted archive. It has no way to distinguish whether the payload contains text, files, or both.
Paranoid mode
In normal mode, the link contains both the secret ID and the decryption key:
/reveal/abc123#decryption-key.
One link, everything in it.
Paranoid mode splits them. You get a link without the key, and the key separately. Send the link over one channel (say, email) and the key over another (say, Signal). Anyone who intercepts only one of the two channels gets nothing usable.
The encryption is identical in both modes. The only difference is how the key reaches the recipient.
Memory handling
After encryption or decryption completes, your browser scrubs the key material from memory using libsodium's memzero. This overwrites the key, the derived key, the salt, the nonce, and the plaintext with zeroes. JavaScript doesn't guarantee when the garbage collector runs, but explicit zeroing reduces the window where sensitive data sits in memory.
What I chose not to build
No accounts. Accounts mean stored credentials, password resets, session tokens, and an authentication system that becomes its own attack surface. Fewer stored things means a smaller target.
No email delivery. If I sent the link by email, the decryption key would pass through my mail server, and the entire zero-knowledge design breaks. You copy the link and send it however you choose.
No detailed audit trail. The access log records that a secret was accessed, from which IP, and whether it succeeded or failed. It does not record who the sender or recipient was, because I don't know.
Transparency
System status is public. Incidents are disclosed with timelines. The FAQ covers the questions people ask most.
Good to know
- If you lose the link, I can't recover the secret for you. I don't have the key.
- If someone else opens the link, that counts against the view limit.
- If you need to explain what the secret is for, send that context in a separate message. Don't put it in the secret itself.
- The entire client-side encryption stack runs in your browser. No plugins, no extensions, no desktop app required.
Use this protocol
Step-by-step guides for the most common use cases:
- ›Share passwords with clients or teammates
- ›Send API keys to developers or contractors
- ›Hand off .env files and configuration secrets