Secure secret sharing for teams
Blog/Zero-Knowledge Architecture

URL Fragment Encryption Keys Explained (RFC 9110 & Zero-Knowledge)

Keith from Cipher Projects12 min read
URL fragment encryption key staying in the browser while the server stores ciphertext only

TL;DR

Zero-knowledge secret links put the decryption key after # because RFC 9110 excludes URL fragments from HTTP requests — the browser keeps #k=... locally; the server only stores ciphertext it cannot read. That solves provider trust, not link theft: the full URL is a bearer secret, and referrers or analytics can leak fragments if misconfigured. Prefer Referrer-Policy: no-referrer. Tools like VanishingVault use this pattern by default.

Why Do Zero-Knowledge Tools Put the Key After #?

The goal is simple: the server should courier ciphertext, not read secrets. If you encrypt in the browser with AES-256-GCM and upload only the encrypted blob, you still need a way to get the key to the recipient without giving it to the host. The URL fragment — everything after # — is the standard answer because HTTP was designed that way.

A typical share URL looks like:

https://example.com/s/abc123#k=BASE64_AES_KEY
  • Path (/s/abc123) — sent to the server; identifies which ciphertext blob to fetch.
  • Fragment (#k=...) — stays in the browser; never included in the HTTP request.

The recipient's browser parses the fragment, downloads ciphertext via the path, and decrypts locally with the Web Crypto API. The operator never sees plaintext or a usable key — which is what “zero-knowledge” means in this product category.

What Does RFC 9110 Say About URL Fragments?

URL fragments are not sent in HTTP requests. RFC 9110 (HTTP Semantics, the current HTTP spec) preserves the long-standing rule: the fragment identifier and its leading # are processed entirely on the client. When your browser requests GET /s/abc123, the server never receives #k=BASE64_AES_KEY — not in the request line, not in headers, not in query strings.

That behavior is why fragment keys work for zero-knowledge design:

  1. Encrypt locally → upload ciphertext only.
  2. Append key after # → browser holds key; server request omits it.
  3. Recipient opens link → same browser-side decrypt path.
  4. Server database dump → blobs without keys.

You can verify this yourself: open DevTools → Network, load a secret link, and inspect the retrieve request. The URL in the request stops before #. Proxies, load balancers, and origin access logs see the same truncated URL.

What the server receives

GET /s/abc123 HTTP/1.1

Path + headers only. Ciphertext ID for lookup. No fragment, no key, no plaintext.

What stays in the browser

#k=BASE64_AES_KEY

Parsed from window.location.hash. Used locally for Web Crypto decrypt. Never uploaded unless the page explicitly sends it (a bug — not part of the HTTP model).

Is a Fragment Key Safe Per HTTP?

Yes for the server-trust problem HTTP was designed to solve. The fragment mechanism gives you a client-only channel without a second round trip. That is why VanishingVault, scrt.link, Bitwarden Send, and other client-side tools use it.

No for every threat. HTTP fragment rules do not make the link magic. The full URL — path plus fragment — is still one bearer credential. HTTP also does not stop you from accidentally leaking the fragment through other channels.

ThreatFragment key helps?Honest limit
Provider / host reads secretsYesServer never gets key — ZK by architecture
Stolen database (ciphertext only)YesBlobs unreadable without fragment keys users hold
Insider with server accessYesNo decrypt path on server if crypto is honest client-side
Leaked / forwarded full URLNoBearer URL — first opener wins; confirm recipient OOB
Referrer leaks to third sitePartialMisconfigured Referrer-Policy can send full URL including #
Analytics / tag managers logging page URLPartialThird-party JS can read location.href including fragment
Compromised or malicious front-end JSNoYou trust the page that runs crypto — supply-chain risk remains

Can Referrers or Analytics Leak the Fragment?

The fragment is not sent to your origin — but other leakage paths exist:

  • Referer header: If a page with Referrer-Policy: unsafe-url (or legacy defaults) links out, the destination may receive the full URL including #k=.... Prefer Referrer-Policy: no-referrer or strict-origin-when-cross-origin on secret-sharing pages.
  • Third-party analytics: Scripts that log window.location.href can capture the fragment client-side and exfiltrate it — HTTP rules do not protect against JavaScript you load on the page.
  • Browser extensions: Any extension with page access can read the hash. Enterprise deployments should document this for high-sensitivity handoffs.
  • Shared clipboards and chat logs: Pasting the full URL into Slack, email, or ticket systems stores path and fragment together in searchable history.

We are not citing a specific breach headline here — this is a known class of misconfiguration risk. Hardening pattern: strict Referrer-Policy, minimal third-party tags on create/reveal pages, and treat the URL like a password. See our Cloudflare architecture post for production Referrer-Policy notes.

Do

  • • Set Referrer-Policy: no-referrer on reveal pages
  • • Share the full URL only with the intended recipient
  • • Confirm receipt on a separate channel
  • • Use tools with an explicit Reveal step (anti-unfurl)

Avoid

  • • Loading heavy ad/analytics stacks on secret pages
  • • Embedding secret links in public wikis or forums
  • • Assuming HTTPS alone hides the fragment from third parties
  • • Splitting path and fragment across untrusted channels without reason

Fragment Keys vs Server-Side Encryption — Which Fits?

ApproachBest forHonest limit
Client-side ZK + fragment key (VanishingVault, scrt.link)High-sensitivity credentials when you want the host blind to plaintextBearer URL; browser-trust; fragment can leak via referrers/analytics if misconfigured
Server-side encryption (OneTimeSecret, typical Password Pusher)Low–moderate sensitivity when you trust the operator or self-hostHost processes plaintext at create/view — not zero-knowledge
Password manager Send (client-side, optional access password)Teams already on Bitwarden; extra passphrase on a second channelSender needs an account; heavier UX for one-off guest handoffs

For why ZK should be the default for one-time handoffs, read why zero-knowledge architecture should be the default. For OTS specifically, see VanishingVault vs OneTimeSecret.

How Does the Fragment Key Flow Work Step by Step?

1

Browser generates AES-256-GCM key

Web Crypto creates a random symmetric key in the sender’s browser — never sent to the server.

2

Encrypt locally, upload ciphertext only

Plaintext is encrypted; only ciphertext + IV + opaque ID reach the API. Server stores an unreadable blob.

3

Append key after # in the share URL

The UI builds https://host/s/{id}#k={key}. RFC 9110 ensures #k= never appears in HTTP requests to the host.

4

Recipient browser decrypts locally

JavaScript reads window.location.hash, fetches /s/{id}, decrypts with Web Crypto, displays once, then burn-after-read deletes ciphertext.

What Should You Trust — and Not Trust — With Fragment Keys?

You can trust

HTTP semantics: fragments stay off the wire. An honest client-side implementation means operators and DB dumps do not get keys. TLS protects ciphertext in transit.

You still must verify

The JavaScript bundle doing crypto (supply chain). Recipient identity (bearer URL). Referrer-Policy and third-party scripts on the page. Link unfurls in Slack/Teams.

Do not assume

That “zero-knowledge” stops link forwarding, screenshots, or a malicious extension. Fragment keys solve provider blindness — not human error.

Frequently Asked Questions

Why do zero-knowledge secret sharing tools put the decryption key in the URL fragment?

Because HTTP does not send URL fragments to the server. Per RFC 9110, everything after # stays in the browser. The tool encrypts in the browser, uploads only ciphertext, and appends the AES key after # so the server never receives a decryptable blob. The recipient’s browser reads the fragment locally, fetches ciphertext, and decrypts with Web Crypto.

Is putting the encryption key in the URL fragment safe per HTTP?

Yes for the server-trust problem: fragments are excluded from HTTP requests, so the host, proxies, and access logs never see the key. The tradeoff is bearer-URL semantics — anyone with the full URL (path + fragment) can decrypt. Referrers and misconfigured analytics can leak fragments to third parties; use Referrer-Policy: no-referrer and avoid embedding secret links in pages that load third-party scripts.

Does the server ever see the URL fragment?

No. Browsers do not include the fragment in GET, POST, or other HTTP requests. The server only receives the path (for example /s/abc123). If you inspect DevTools Network tab, the request URL stops before #. That is why a stolen database of ciphertext is useless without the fragment keys that were never uploaded.

Can analytics or referrers leak the URL fragment?

They can if misconfigured. The fragment is not sent to your origin server, but a page that loads third-party analytics with full page URLs, or a Referrer-Policy that forwards the full URL to another site, can expose #key= to someone other than the recipient. Prefer strict Referrer-Policy, avoid pasting secret links into pages with heavy third-party tags, and treat the full URL as confidential.

Is the full URL a bearer secret?

Yes. Whoever opens the complete URL first can decrypt the secret. There is no separate login step after the link is created. Confirm the recipient out of band, avoid public channels, and rotate the credential if you suspect the wrong party viewed it. Zero-knowledge protects against provider access — not against a leaked link.

What is the difference between the URL path and the fragment?

The path (before #) identifies the ciphertext on the server — for example /s/abc123. The fragment (after #) holds the decryption key — for example #k=base64key. The path is sent in HTTP requests; the fragment is not. Both parts are required to read the secret, which is why you must share the entire URL.

Do all one-time secret tools use URL fragment keys?

No. Tools that encrypt on the server (classic OneTimeSecret, many Password Pusher deployments) never need a fragment key because the server holds encryption keys. Client-side zero-knowledge tools (VanishingVault, scrt.link, Bitwarden Send) put keys in the fragment or an equivalent client-only channel so the provider stays blind.

What happens if someone loses the URL fragment?

The secret is unrecoverable — by design. The provider never stored the key, so there is no password reset or admin recovery. That is the privacy tradeoff: no backdoor for operators also means no backdoor for users who lose the link.

Try fragment-key secret sharing with VanishingVault

Client-side AES-256-GCM, key in the URL fragment, ciphertext destroyed after one view. The server never sees your secret or your key.

Create a zero-knowledge link