Fugax
Fugax shares a snippet, a note or a file once and then forgets it. The text is encrypted in the browser with AES-256-GCM and the key rides in the URL fragment, which a browser never sends to a server. The server stores bytes it cannot read and deletes them on a timer or after a set number of reads.
- 2026
- Encryption
- CLI
- Next.js
- Tests
In short
- Status
- Built through all 11 planned phases. Not deployed yet, and not independently audited
- My part
- Product, interface, architecture and review, with contributions from one other developer. Built with AI agents
- Encryption
- AES-256-GCM, keys derived with HKDF-SHA256, optional passphrase through Argon2id
- Clients
- Web, CLI, VS Code extension, browser extension, embed script: one core package
- Secret scanner
- 13 patterns in the browser, with checksum checks for Turkish ID numbers and IBANs
- Stack
- Next.js 16, React 19, TypeScript, Drizzle, MySQL, Vitest
- Tests
- 22 test files, including 34 integration cases against a running server and a real database
The key never reaches the server
A share link looks like an address with something after a hash sign. That last part is the key, 32 random bytes, and browsers do not send it: not in the request and not in the Referer header. The server receives the ciphertext, the nonce, the salt and the policy, and nothing that opens them. Link and key can also be sent separately, over two channels.
The policy, meaning burn after reading and the expiry time, goes into the encryption as authenticated data. If a server changed either, decryption would fail instead of quietly obeying. Attachments get their own derived keys, and the number of chunks is authenticated too, so a truncated file is caught.
A passphrase, when there is one, goes through Argon2id and is combined with the link key rather than replacing it. The Argon2 code loads only when a passphrase is used.
Burning, without races and without bots
Reading a share is a POST, not a GET. Chat apps fetch every link to draw a preview, and a GET would let that preview burn a one-time message before its reader arrived. Opening also takes a row lock, so two requests landing together cannot both pass a limit of one read.
A share that is gone answers 410 and one that never existed answers 404, because those are different facts. A purge job then removes expired rows, their files on disk, uploads that were announced and never finished, and folders left behind by either.
One envelope, five clients
The web app, the command line tool, the VS Code extension, the browser extension and the embed script all seal and open the same envelope, because all of them import the same core package, and a cross-client test keeps it that way. Three copies of the crypto would have drifted apart one day.
Before anything is sent, a scanner in the browser looks for 13 kinds of secret, from cloud keys and tokens to private keys and connection strings. Turkish ID numbers and IBANs are checked against their checksums, so a random 11 digit number does not raise an alarm. It warns and suggests a shorter life for the share. It does not block.
What the database does not hold
No plain text, no file names, no language, no title, no raw IP address, no user agent, no referrer. Rate limiting uses a keyed hash of the address with a salt that rotates daily. The delete token stays in the browser that created the share, and the server keeps only its hash.