# Why the encryption key sits in the URL fragment, and where it leaks

> How a URL fragment encryption key works: browsers never send the part after # to the server. Where fragment keys still leak, and what limits the damage.

Source: https://evergist.com/blog/url-fragment-encryption-key-explained/
Published: 2026-09-30

**Short answer:** a URL fragment encryption key is a decryption key stored in the part of a link after `#`. Browsers keep that part to themselves and leave it out of the HTTP request, so the server that stores the encrypted note never receives the key. The key can still leak from the places the full link is kept: browser history, chat logs, screenshots, extensions, and the page's own JavaScript.

## What is a URL fragment?

It is everything after the first `#` in a URL. In an Evergist link, the path names the note and the fragment is the key.

```text
https://evergist.com/g/7mQx2Ld9KpTa#pX3v...43 characters...
        \__________/ \____________/ \_____________________/
            host         path              fragment
      sent to the server (over TLS)     stays in the browser
```

The original use is jumping to a heading inside a page. `#installation` tells the browser where to scroll once the page has loaded. The server has no use for it, so the standards keep it on the client.

## Does the server ever see the URL fragment?

No. A browser that follows the HTTP standard removes the fragment before it sends the request. Three documents say so.

- [RFC 3986, section 3.5](https://www.rfc-editor.org/rfc/rfc3986#section-3.5), which defines URLs, says "the fragment identifier is separated from the rest of the URI prior to a dereference" and is "dereferenced solely by the user agent".
- [RFC 9110, section 7.1](https://www.rfc-editor.org/rfc/rfc9110.html#section-7.1), which defines HTTP, says "The target URI excludes the reference's fragment component, if any, since fragment identifiers are reserved for client-side processing."
- [MDN](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Fragment) puts it plainly: "The fragment is not sent to the server when the URI is requested; it is processed by the client (e.g., the browser) after the resource is retrieved."

The `Referer` header is covered too. RFC 9110 section 10.1.3 says a user agent "MUST NOT include the fragment" when it builds that header.

Check it yourself. Open a note with the browser's Network tab open and read the request URLs. None contains the text after `#`.

## Why put the key there?

Because it lets one link do two jobs. The path tells our server which ciphertext to return. The fragment tells the recipient's browser how to decrypt it. The recipient needs no account, no app, and no second message.

A query string (`?key=...`) would not work. Query strings are part of the request, so the server and any proxy that terminates TLS would see the key.

## How does Evergist use the fragment?

The fragment holds `K`, 32 random bytes generated on your device. Nothing else in the design is secret from us, so everything depends on `K` staying out of our reach. Per [our encryption scheme](https://evergist.com/security/#the-encryption-scheme):

1. Your browser, the CLI, or the local MCP server generates `K` and derives an AES-256-GCM encryption key from it with HKDF-SHA256.
2. It derives a separate access token from `K` and uploads only the SHA-256 hash of that token with the ciphertext.
3. The server returns an ID. The client builds `https://evergist.com/g/<id>#<K>` locally.
4. The reader's browser takes `K` from the fragment, derives the same access token, fetches the ciphertext, and decrypts on the device.

Step 2 means someone who sees only the path, a corporate proxy for example, can't download the ciphertext or use up a view. Without the token derived from `K`, the server answers as if the note didn't exist.

## Where do fragment keys leak?

Anywhere the full link is stored or shown. "The server can't see the key" is a statement about the network. It says nothing about the devices at either end or the channel that carried the link.

| Where | How the key gets there | What limits it in Evergist |
|---|---|---|
| Browser history | The browser saves the whole URL, fragment included | One view. The link in history opens nothing once the note is read |
| History sync | If you have sync turned on, history entries go to your other devices and the sync provider | One view and a short expiry |
| Chat and email history | You pasted the link there. That copy has the key in it | One view, short expiry, a password sent through another channel |
| Agent transcripts | A link given to an AI agent is saved with the conversation | One view. `evergist status` checks a link without spending it |
| Screenshots and screen shares | The address bar shows the full URL | One view. Crop the address bar |
| Browser extensions | An extension allowed to read your tabs can read the URL | A password typed into the page is not in the URL. An extension that reads page content gets the note text either way |
| JavaScript from the server | The page's script reads the fragment to decrypt. Altered code could send it somewhere | See the next section |

RFC 9110 lists the extension and script case in its security considerations (section 17.11): fragments "will be visible to the user agent and any extensions or scripts running as a result of the response."

Our viewer leaves the key in the address bar after it opens a note, so a reload still works on notes with views left. The link stays in your history.

## What if the server sends bad JavaScript?

Then the fragment does not protect you. The script that decrypts the note comes from our server and runs with access to `location.hash`. If someone took over the server and changed that script, it could read the key and send it out.

PrivateBin's README gives its users the same warning about trusting the server administrator. Other fragment-key tools share the limit, and our [comparison of private note tools](https://evergist.com/blog/private-note-sharing-compared/) lists which ones use this design.

What we do about it, from the security page:

- The site's Content-Security-Policy sets `connect-src 'self'`, so the page can send requests only to evergist.com.
- The page loads no third-party scripts, fonts, or analytics.
- The JavaScript is not minified. You can read it in the Sources tab.

Those make tampering detectable, not impossible. If that gap matters for what you are sending, skip the browser. The [CLI](https://evergist.com/agents/) is a single file you can download, read, and pin, and the [Python reader](https://evergist.com/examples/evergist_read.py) decrypts a note without any of our code.

## Which mitigations does Evergist have?

**One view.** Set the view limit to 1 and the server deletes the note during the first read. Every copy of the link left in history, a chat, or a screenshot points at nothing. Each note handles one request at a time, so two readers can't both get the last view.

**Expiry.** Notes last from one minute to 30 days. An unread note with a one-hour expiry is exposed for an hour.

**A password mixed into the key.** This is the one that covers a leaked link. The client stretches the password with PBKDF2-SHA256 at 600,000 iterations and combines the result with `K` before deriving the encryption key. A person holding only the link can't decrypt. The salt comes from `K`, so the server can't precompute guesses, and ten wrong password attempts delete the note.

```sh
printf '%s' "$SECRET" | npx -y evergist create --views 1 --expires 1h --generate-password
```

Send the link in one channel and the password in another. A password in the same message as the link adds nothing.

None of this helps after a legitimate read. The recipient has the plaintext and can copy it anywhere.

[How to share a password securely](https://evergist.com/blog/how-to-share-a-password-securely/) covers the human side, and [do link previews burn one-time links](https://evergist.com/blog/do-link-previews-burn-one-time-links/) covers bots that open links before people do.

## Questions

### Are fragment keys safe?

Against the server and the network, yes, as long as the page's JavaScript is honest. Against anyone who obtains the full link, no. The link is the key. Treat it like the secret it unlocks until the note is read or expired.

### Does a password help if it travels with the link?

No. It is no better than having no password. The password covers the case where the link leaks. If both sit in the same thread, one leak exposes both. Send the password by a different route, such as a call or a different app.

### Is this zero-knowledge link sharing?

People use that phrase for this design. We prefer the concrete claim. The server stores ciphertext and token hashes, and never receives the key, the password, or the plaintext. [Verify it yourself](https://evergist.com/security/#verify-it-yourself) lists four ways to check that without trusting us.

## Next steps

- Agent instructions, with every command: https://evergist.com/SKILL.md
- MCP server, CLI, and REST API setup: https://evergist.com/agents.md
