dynamic-preauth

A Rust proof of concept that stamps a unique identity into an executable as it downloads, so the program authenticates itself on first run with no user input.

Proof of Concept Maintained Updated 3d ago

dynamic-preauth is a proof of concept for pre-authenticating a downloadable executable: the server stamps a unique identity into the binary in the moments before it streams to the user, so the program authenticates itself the first time it runs — no login, no config file, no interaction. The server is Rust (Salvo); the demo frontend is Astro with React islands.

It inverts the usual direction of authentication. Normally the user proves who they are to the server. Here the server writes who you are into the artifact you download, and the artifact carries that identity back on first run.

The patch site

Recompiling the executable per user would be the obvious way to embed an identity, and far too slow to do on every download. So the binary is compiled once, ahead of time, with a fixed marker planted inside it: a 1024-byte run of the letter a. A build script writes that marker into a JSON blob the program pulls in with include_str!, so the run of bytes ends up sitting in the compiled binary as a constant.

When the server boots, it reads each platform's binary into memory and scans byte-by-byte for that 1024-byte run, recording where it starts and ends. From then on, serving a personalized copy is a single in-place overwrite: mint a random u32 token, write its digits over the marker, pad the remainder with spaces, and stream the result. The patch is exactly as long as the marker it replaces, so nothing downstream shifts — no offsets to fix up, no headers to recompute.

The round trip

Run the downloaded program and it reads the token back out of its own bytes and fires a single request at the server: POST /notify?key=0x.... The server matches that token to the session that downloaded it and pushes a TokenAlert down every WebSocket the session has open. In the browser, the matching download lights up and a chime plays. The whole loop — download, run, notify — happens with no input beyond running the file.

Before it calls home, the program SHA-256-hashes its embedded marker and compares it against a hash baked in at build time, which tells it whether it was patched at all. An unmodified binary still carries the original run of as, and knows it was never personalized.

Tab coordination

One detail grew well past its station: deciding which browser tab gets to play the notification sound. A session can have several tabs open, all sharing the same WebSocket-backed state, and the alert fans out to all of them. Only one should chime.

The tabs elect a leader over a BroadcastChannel. Each broadcasts a heartbeat every 250 ms; a tab unheard from for 750 ms is treated as dead; the leader is whichever live tab was focused most recently. When the alert arrives, the leader tries to play the sound — but browsers refuse audio in a tab that has never been interacted with, so the attempt can fail. If it does, it cascades: the failed tab announces the failure, the next-best tab tries, then the next, until one succeeds or every tab has refused.

Limitations

The identity is a random 32-bit token, small enough to guess and trivial to forge; /notify is unauthenticated, so anyone presenting a valid token triggers its alert. The tamper-check hash is embedded in the same binary it is meant to protect, so a determined patcher just rewrites it too — the build script says as much in a comment. Stamping bytes into a finished executable breaks code signing outright. And the server holds everything in memory: every session, token, and loaded binary lives in one mutex-guarded map, so a restart wipes all of it.

Why the demo shows its own build logs

Railway does not surface build logs on public projects, so the server fetches its own through Railway's GraphQL API, strips the ANSI color codes, trims everything after the build finishes, and serves the result at /build-logs for the demo page to render.

§05

Gallery

Built with
Astro Demo Experiment Rust Security TypeScript
On this page