Some things should only be read once — a database password, a recovery phrase, the door code for the studio. lilsecret is a tiny open-source app for sharing them as sealed drops: the recipient gets one link and a one-time code, and after one read (or three wrong guesses, or a missed deadline) the drop erases itself. It lives at lilsecret.norik.io — and below is the live app itself.
One link, one code, one read
A drop is a handful of plain-text notes in a simple line editor. Nothing fancy — titles, line numbers, and text that stays exactly as typed.
Then you pick the burn conditions: how long the reading window stays open once it’s unlocked (immediately up to 30 minutes), and how long an unopened drop survives before it shreds itself (1 to 48 hours). Sealing produces a single-use link plus a six-digit code, or a passphrase the recipient already knows. Relay them through different channels: the link in chat, the code over a call.
The recipient gets three tries. On the last mistake the drop self-destructs, and every revisit of a dead link gets an honest answer — already opened, expired unread, burned by hand, or self-destructed. Nothing is archived; what remains only says how it ended.
Two halves of one key
The part I care most about: the server cannot read a drop — and neither can anyone who steals its database. Notes are encrypted in the browser before anything travels, and the key is derived from two halves:
key = HKDF( PBKDF2(code, salt) + linkKey )
code — the 6-digit code or passphrase, relayed by you
linkKey — random bytes living only in the URL fragment (#…)
browsers never send fragments to servers
The link alone faces a three-attempt limit refereed by the server. The database alone holds ciphertext and a hash — brute-forcing every six-digit code gets you nothing without the link half. Only link + code opens a drop, which is exactly why you send them separately.
A server that cannot peek
The whole backend is one zero-dependency Node file: node:http for the API, node:sqlite for storage. Every record is sealed again server-side with a key that lives in a Kubernetes Secret rather than on the volume — a leaked disk snapshot reveals nothing, not even the burn policies. On a successful unlock the payload leaves the server exactly once and is wiped on the spot.
Design first, then a day of building
The app started as a Claude Design canvas — the noir look, the fingerprint waves, every screen from the composer to the flame on the burned page. Implementation kept the design’s markup nearly verbatim and swapped the prototype’s localStorage for the real protocol: PBKDF2, AES-256-GCM, and a verifier hash so the server can referee wrong codes without ever being able to decrypt. Tests run in-process with node:test at ~90% line coverage on the server, and CI ships every deploy to Kubernetes.
Run your own
git clone https://github.com/NorikDavtian/lilsecret.git
cd lilsecret && node server.js --port 8789
MIT licensed. No accounts, no cookies, no trackers. The container listens on 8080, wants a 64-hex STORAGE_KEY secret, and keeps its SQLite file in /data — hand those three facts to your favorite LLM and it will land it on your infra. The README has the full sealing story. And please — don’t use it for shady shit.