The Fragment Guarantee
RFC 3986 §3.5 defines the URI fragment as the portion after the # character. It has a special property that makes it uniquely suited for encryption key transport:
The fragment is never sent to the server.
This isn't a convention or a best practice — it's part of the HTTP specification. When a browser requests https://example.com/page#secret, the HTTP request contains only GET /page. The #secret part stays in the browser. It's not in the request headers, not in the URL path, not in the query string. The server literally never sees it.
Why This Matters for Encryption
Host-blind encryption systems need to solve a fundamental problem: how do you give the recipient a decryption key without also giving it to the server?
Common approaches:
- Separate channel: Send the key via a different medium (Signal, email). Awkward and error-prone.
- Key exchange protocol: Diffie-Hellman or similar. Requires both parties to be online and adds complexity.
- Password-based: Recipient enters a shared password. Requires pre-coordination.
- URL fragment: Embed the key in the URL itself. One link, zero coordination, server-blind by specification.
The URL fragment approach is the only one that requires no pre-coordination between sender and recipient, while guaranteeing the server never sees the key.
How vnsh Uses Fragments
When you encrypt content with vnsh, the output is a URL like:
https://vnsh.dev/v/aBcDeFgHiJkL#R_sI4DHZ_6jNq6yqt2ORRDe9kL2mN3pQ4rS5tU6vW7xY8zA9bC0dE1fG2hI3jK
Everything before # is the blob identifier. Everything after # is the base64url-encoded encryption key + IV (48 bytes = 64 characters). When someone opens this URL:
- Browser sends
GET /v/aBcDeFgHiJkLto vnsh.dev — no key in request - Server returns the encrypted blob — it cannot decrypt it
- Browser JavaScript reads
window.location.hashto extract the key - Browser decrypts the blob client-side using WebCrypto
The server is a "dumb pipe." It stores encrypted blobs and serves them back. Even under subpoena, it can only produce random-looking binary data.
What About Server Logs?
A common concern: "Don't web servers log the full URL including fragments?"
No. Web servers log the request URI, which by HTTP specification excludes the fragment. Check your Nginx or Apache access logs — you'll never see a # in them. The fragment is a client-side construct that the server never receives.
However, fragments can appear in:
- Browser history: The full URL with fragment is stored locally. This is by design — the recipient needs the key.
- Referer headers: Historically, browsers could leak fragments in the
Refererheader when navigating away. Modern browsers strip fragments fromReferer(per the Referrer Policy spec). vnsh pages setreferrerPolicy: no-referreras an additional safeguard. - Browser extensions: Malicious extensions with
<all_urls>permission can readwindow.location.hash. This is a browser-level trust boundary, not something vnsh can mitigate.
Comparison With Other Key Transport Methods
Query Parameters (?key=abc)
Query parameters ARE sent to the server. They appear in access logs, CDN logs, and analytics tools. Never use query parameters for encryption keys.
HTTP Headers (X-Decrypt-Key: abc)
Custom headers require the client to make an explicit API call rather than just opening a URL. This breaks the "one link" user experience and requires JavaScript before any content can be fetched.
Out-of-Band (separate message)
Sending the key through a different channel (Slack DM, email) is secure but requires coordination. The recipient needs two things instead of one. In AI workflows, this is a non-starter — you can't send Claude a separate Slack message with the key.
Client-Side Derivation (PBKDF2 + password)
Derive the key from a shared password. Secure if the password has enough entropy, but requires the sender and recipient to agree on a password. Again, doesn't work for AI agents.
The AI-Native Advantage
URL fragments are particularly powerful for AI coding workflows because:
- Single artifact: One URL contains both the content reference and the decryption key. Paste one thing, AI gets everything.
- MCP-compatible: The vnsh MCP server receives the full URL including fragment from the conversation, fetches the blob, extracts the key, and decrypts locally. The AI model itself never needs to "visit" the URL.
- No auth flow: No tokens, no login, no API keys needed for reading. Just the URL.
- Self-expiring: When the blob expires (24h default), the URL becomes inert. The key in the fragment is useless without the ciphertext.
Threat Model
What the URL fragment approach protects against:
- Server compromise: Attacker gets encrypted blobs with no keys. Useless.
- Network interception: HTTPS encrypts the full request. The fragment isn't even in the request to intercept.
- Subpoena/legal request: Server operator can only produce encrypted blobs. Keys never touch the server.
- Server-side logging: Fragments are excluded from HTTP access logs by specification.
What it does NOT protect against:
- Link sharing: If you paste the full URL in a public Slack channel, anyone can decrypt it. The link IS the key.
- Browser compromise: Malware on the recipient's machine can read the fragment from the address bar or DOM.
- Shoulder surfing: The full URL is visible in the address bar.
This is an intentional trade-off. vnsh protects against server-side threats (the most common and scalable attack vector), not client-side threats (which require targeting individual users).
Implementation Notes
If you're building your own fragment-based encryption system:
- Use base64url encoding (RFC 4648 §5) for the key material. Standard base64 contains
+and/which can cause URL parsing issues. - Set a strict Referrer Policy:
no-referrerorsame-originto prevent fragment leakage in navigation. - Set a strict CSP: Prevent inline scripts and third-party JavaScript from reading
window.location.hash. - Don't log client-side: If you have analytics JavaScript, make sure it doesn't send
window.location.hashto your analytics service. - Use HTTPS only: Without TLS, the request path and headers are visible. The fragment is still hidden from the server, but a network attacker could inject JavaScript to read it.
vnsh is fully open source at github.com/raullenchai/vnsh. See how we implement all of the above in a production system.