I built a zero-knowledge secret sharing tool. Text and files are encrypted in the browser with AES-GCM 256 before upload - the server only ever sees ciphertext.
The decryption key lives exclusively in the URL fragment (#k=...). URL fragments are never sent in HTTP requests (RFC 9110 §4.2.3), so Cloudflare Workers, D1, and R2 never see it - even in logs.
A few things I tried to do right:
- Single-use by default: Durable Objects handle atomic read→decrement→delete with blockConcurrencyWhile, no race conditions on concurrent requests
- Paranoid mode: returns not_found instead of expired/burned, no timing oracle
- Revoke endpoint: delete a drop before it's read using a SHA-256'd token with constant-time comparison
- CLI: burnafter send / burnafter receive - full E2E from terminal, key never touches a browser - /security page with a live in-browser AES-GCM demo and a manual Node.js decryption snippet so you can verify without trusting me
Stack: Cloudflare Workers + D1 + R2 + Durable Objects. No third-party crypto libs.
Live: https://burnafterread.casablanque.com
Source: https://github.com/casablanque-code/burnafterread
Verify: https://burnafterread.casablanque.com/security
Top comments (0)