How it works
Runnel cannot read your links at rest — not will not, cannot. Your browser encrypts a destination before it reaches us. What we keep is ciphertext we hold no key for. Everything else Runnel does stands on that blindness: revoke in seconds, expiry, use limits. This page shows the design and states every exception before you have to hunt for it. It ends with how to check us.
The link, anatomized
https://runnel.link/s/K3kiAUzn2fovMy8tBvCoxA#r.eHW87bGAYIvx9tZjvcF67cCGgzhPeO1eXm4qT8sKw2d
The id: the only part of the URL our servers ever see on a visit.
The key: it rides the #fragment, and browsers never send fragments over HTTP.
In redirect and mask modes the key never leaves your browser. An open sends only the key's SHA-256 fingerprint, the proof that burns a use. Those links reject a full key outright as wrong-shaped. Proxy mode works differently. It is exception two below.
The letter after # names the delivery mode: r redirect, i mask, p proxy.
What we store, what we cannot read
We store
- The link id and its delivery mode
- The destination, as AES-256-GCM ciphertext
- A SHA-256 fingerprint of each key: the burn proof's other half
- Your key envelope: wrapped blobs, plus the parameters to re-derive your wrapping key
- Expiry, use counts, and plan usage counters
- An access record for every open: IP address, user agent, country, bytes, time, as our Privacy Policy states. Your dashboard sees country and coarse client. The API never serializes the raw values
We cannot read
- Where any link points, at rest: ciphertext, no key
- Your unlock key: no passphrase, PRF output, or raw master key ever crosses the wire
- The contents of your envelope: we store it, we cannot open it
- Anything from your sign-in password: it is fully decoupled from encryption
The honest exceptions
Five moments where the picture above needs a footnote. Here they are, before you find them.
When you mint
Creating a link sends us the destination once, in plaintext, so we can turn abuse away at the door. We run three checks: internal addresses, an exact threat-blocklist match, and a reachability probe. We never store the destination. Nothing that lands in the database can reproduce it: ciphertext, and one-way hashes. A service that could not see a target even for that moment could not refuse phishing links either. We chose to refuse them.
Proxy opens
In proxy mode the recipient's browser sends the key with every open. We use it in memory to decrypt the destination and stream it back. Both go out of scope when the response ends: we never write or log the key or the destination, on any branch. The open still writes the same access record every mode gets. That is the price of hiding the destination from the recipient: someone has to dereference it, and transiently that someone is us.
An assistant you authorize
Connect an AI assistant over MCP and tick "Allow reading destinations", and you have granted a real server-side read. We store your view key's private half encrypted under that connection's token. The assistant's requests then make our server decrypt destinations on its behalf. It is off by default and revocable in one click: revoke the connection and the capability dies with it. Plain API keys are different. Their reveal scope returns still-sealed blobs, and the server never decrypts on that path.
You, the creator
"Nobody can recover a destination" would be false for exactly one person: you. Your browser also seals each link's key to your account's view keypair, so your own unlocked dashboard can show you where your links point. The worker that serves links to everyone else never reads that column.
The key's fingerprint
Every open sends a SHA-256 fingerprint of the key. That is how a link burns a use only for the genuine key. The fingerprint is one-way: no one can recover the key from it. But "we see nothing derived from your key" would be false, so we tell you this instead.
Three ways a link can open
Who can learn a destination depends on the mode you mint.
| Mode | The recipient learns it | Runnel learns it at open |
|---|---|---|
| Redirect | Yes: the browser goes there | No: we get only the fingerprint |
| Mask | With modest effort (devtools) | No: we get only the fingerprint |
| Proxy | No: we fetch and stream it | Yes: transiently, in memory, never stored |
Proxy mode also protects the visitor from the destination. Our worker fetches with its own egress address and a generic browser fingerprint, so the destination site never sees the visitor's IP or user-agent. That is a separate promise from blindness. It points the other way.
No oracles
Whether a link exists is not answerable from the outside. Every /s/ address serves the same byte-identical shell and reads nothing from the database until you open. Every failure is the same byte-identical 404: revoked, expired, spent, never-existed.
The abuse-report form thanks you identically whether or not the URL matches one of ours. The open path emits no diagnostic logs. Every error branch is silent. The only write is the access record described above, and it carries the outcome, never the key. One honest bound: these are guarantees about response content. We do not claim immunity to fine-grained timing analysis.
Check us
You do not have to take this page's word. The encryption runs in JavaScript your browser downloads, and you can read it. Open devtools on a redirect link and watch the network panel: the request carries a fingerprint, never the key. Your unlock key never leaves your machine. What crosses the wire is wrapped blobs and derivation parameters, nothing that unwraps them. Our Terms and Privacy Policy describe the same architecture, exceptions included. If you find our behavior and this page disagreeing, that is a bug we want to hear about: report it.