Secretus logoSecretus

How one-time secret links actually work: the URL fragment trick, explained

·6 min read

One-time secret links look like magic the first time you meet them: you paste a password, get a URL, and the service swears it cannot read what you wrote — even though it's hosting it. No browser plugin, no shared key ceremony, no account needed on the receiving end. The whole trick rests on a browser behavior older than most of the web: the URL fragment.

The # that never leaves your device

A URL like example.com/s/abc123#async_Jx9… has two very different halves. Everything before the # — the path — is sent to the server with every request. Everything after it — the fragment — is not. That's not a product feature; it's how HTTP has worked since the 1990s. Fragments were designed to scroll you to a section of a page, so browsers keep them strictly client-side: they never appear in the request, in server logs, or in standard proxy logs.

That one rule makes an elegant design possible: put the decryption key in the fragment. The sender's browser generates a random AES-256-GCM key, encrypts the secret locally, uploads only the ciphertext, and appends the key to the link after the #. When the recipient opens it, their browser downloads the ciphertext, reads the key from the fragment locally, and decrypts in the page. The service in the middle stores and serves ciphertext it has no key for.

Why "one-time" is the other half of the design

A link that carries its own key is a bearer capability: whoever holds the complete URL can read the secret. That's also its weakness — links get forwarded, previewed, and logged in chat apps. The mitigation is burning the ciphertext on first read. After one successful retrieval, the server deletes what it stored; the same link opened a second time returns nothing to decrypt. A stolen link that has already been used is a dead end — and a link that arrives already-consumed tells the sender something is wrong.

Expiry completes the model: a secret that's never opened shouldn't wait around forever, so unopened ciphertext is deleted on a timer measured in minutes or days, not months.

What this model protects against — and what it can't

Against a database leak, the design holds up remarkably well: an attacker who copies the server's storage gets ciphertext without keys. Against a curious operator, likewise — there is nothing readable to be curious about. This is why the architecture is often called zero-knowledge for this mode: the knowledge genuinely isn't there.

Honesty requires the other list too. The model does not protect against someone who intercepts the complete link before the intended recipient opens it — that's why the channel you send a link through, and a short expiry, still matter. It doesn't protect against malware on either endpoint, or a recipient who copies the secret after legitimately opening it. And it requires trusting the JavaScript the service ships, since the encryption runs in the page. No honest vendor will tell you a bearer link is unconditionally safe; the claim is narrower and more useful — the server, its backups, and its operators never hold what's needed to read your secret.

Why this beats the alternatives for one-off sharing

Compare the failure modes. A password pasted in chat lives in that channel's history and backups indefinitely — this week's incident reporting is full of exactly that pattern. An email attachment is an archive entry on at least two servers. A password manager's shared vault is excellent for long-lived team credentials, but overkill and over-persistent for "here's the Wi-Fi key for the contractor". The one-time link occupies the gap: transport for a secret that should exist in transit briefly, be readable once, and leave an auditable puff of smoke instead of a copy.

The next time a tool hands you a link with a long string after the #, you'll know exactly what you're holding: the key itself, riding in the one part of the URL the internet never sees.

Share a secret the safe way

End-to-end encrypted, one-time links — free, no account needed.

Try Secretus