When an ops engineer needs to hand a colleague a database password, an API key, or a recovery code, the cheapest move is to paste it into Slack or email. The message is searchable. It syncs to every device. Six months later it is still there. Switch to a “one-time link” and many people still write the decryption key as ?key= after the ciphertext id. Then the host, the reverse proxy, and the access log all see the key. Encryption is only half done.

This is not a tour of how to click through a self-destructing form. That belongs on the tool page. The question is narrower: when you send a password once, why the decryption key belongs after # in the URL, what the server therefore never sees, and where that protection stops. An earlier piece covered how to verify in Network that plaintext was not uploaded. Here the same panel answers a different object: which slice of the URL the key lives in. MyPassGen’s one-time link follows that boundary: AES-256-GCM finishes in the browser, the server parks ciphertext only, the key sits after #, and create and read both open without an account.

Split the URL into two objects first

Everything after ? is the query. It rides on the HTTP request line and lands in the far end’s access logs. Everything after # is the fragment. The protocol leaves it for the client. Put the key in the wrong slice and every later claim of “zero knowledge” falls over.

Three ways to send the same password

Take one disposable test password, orange-lake-7. You can send it at least three ways. The difference is not the slogan on the page. It is whether plaintext became ciphertext first, whether the key entered HTTP, and whether you can still search the original six months later.

Method What the host or the chat app receives What you can still find later
Paste the password into chat Full plaintext A searchable original
Encrypted link, key written as ?key= Ciphertext and key (both on the request) The key in access logs; the full URL in chat
Encrypted link, key after # The server sees only a ciphertext id; the chat app may still store the whole URL No key in server logs; the full link may still sit in chat history

The third row only answers “the machine that hosts ciphertext should not receive the key.” It does not answer “will the chat app store the whole URL.” The full link is a credential: anyone who copies the address with # can decrypt in a browser. Putting the key after the hash solves server trust. It does not solve a forwarded link.

Short text is the right shape for this path. MyPassGen caps a single note at 32 KB — passwords, key fragments, and a short instruction. A certificate pack, an exported sheet, or anything above that size belongs in the file encryption box: encrypt locally to .lock / .enc, then send the passphrase on another channel. The file case is covered in who can read plaintext before a cloud upload.

What HTTP actually transmits

RFC 3986 §3.5 names the part after # a fragment identifier. It points at a secondary location the client interprets after the primary resource arrives. The browser fetches by path and query first. Once the document lands, the fragment decides a scroll position or is handed to page script. The HTTP request itself does not need that slice.

Current HTTP semantics are stricter. RFC 9110 §7.1 says the target URI excludes the reference’s fragment component, because fragments are reserved for client-side processing. A legal origin-form on the request line is the path plus an optional query. There is no # production. You may see s.html?id=abc#the-key in the address bar. The document request that leaves the tab should be GET /en/s.html?id=abc. The slice after the hash stays on this machine.

Referer strips the fragment too. MDN Referer says the header may carry origin, path, and query. It must not carry a URL fragment or a username and password. The W3C Referrer Policy likewise empties the fragment in the strip-for-use-as-a-referrer step. So “put the decryption key after # and the host that parks ciphertext never receives it over HTTP” is protocol behavior. It is not a vendor promise.

The question mark and the hash are not interchangeable

Per the WHATWG URL Standard, when you open http or https, the part after ? enters the request line. Write s.html?id=abc&key=the-key and the key appears in access logs, reverse proxies, and some CDN records. Which marketing parameters you can drop when you share a public link is a different problem — see which URL parameters to strip. A decryption key must never move into the query.

There is also an encoding trap. %23 is the percent-encoding of #. A %23 written in the path or the query is sent to the server and decodes to a literal hash character. It is not a fragment. The key must sit after an unencoded # in the address bar, not after a %23 folded into the path.

What drops out of server logs

A complete one-time send splits into two steps. On create, the tab builds a 32-byte random key, encrypts the plaintext with AES-256-GCM (12-byte IV, stored with the ciphertext), then POSTs ciphertext, a TTL, and a read count. On read, the script takes the key from location.hash, asks the server for ciphertext only, and decrypts on this device. The link looks like s.html?id={id}#{key}: the query holds only the id; the key lives only after the hash.

NIST SP 800-38D specifies GCM as authenticated encryption: if the ciphertext is altered, decrypt should fail. You do not get a garbled body that you might treat as the note. RFC 5116 AEAD_AES_256_GCM uses a 32-byte key, a 12-byte nonce, and a 16-byte authentication tag. MDN’s AesGcmParams likewise recommends a 96-bit IV, and you must use a fresh IV for every encryption under the same key. Those numbers are checkable in an implementation.

So server logs should show: the ciphertext id, the create JSON fields ciphertext / ttl_hours / max_reads, and the later GET for ciphertext. They should not show: plaintext, the 32-byte key, or the slice after # in the address bar. TTL can be 1 hour, 24 hours, or 7 days, or the note can burn by read count alone. Reads range from 1 to 10. Those are constraints on the create page. They are not a service-level promise invented after the fact.

The protocol does not police what page script reports

HTTP not sending the fragment does not mean the key never leaves this computer. Page script can read window.location.hash and fetch it on its own. If analytics reports the full location.href as the page URL, the slice after the hash enters the stats log. Read the analytics request in Network. Do not stop at “HTTP will not send it.”

The full link is still a credential

Putting the key after # only hides it from the HTTP service that hosts ciphertext. Chat apps, mail clients, browser history, and synced tabs store the URL the user saw, hash included. Whoever holds the full link can open the reading page and decrypt. That is a bearer URL: the link itself is the credential. There is no second “password only the recipient knows” unless you add one by hand.

For a more sensitive handoff you can split the two halves: one message carries only s.html?id=…; another channel — a phone call, a desk conversation, or a second messenger — carries only the slice after #. Neither half decrypts alone. That is a usage pattern, not the product default. The default still emits one complete link so the other person can open it once.

It also cannot stop a screenshot, a copy, or a forward of the plaintext. A one-time link reduces repeat opens and long-lived server-side plaintext. If the recipient screenshots the note or pastes it into another channel, the protocol cannot help. Use it when you trust the other person to read once and stop, and when the secret can be rotated and resent. A long-lived master password, a private key, or a seed phrase should not travel this way.

Check it in Network

The steps below do not depend on a brand claim. Use a disposable test sentence. Do not practice with a live database password.

  1. Open DevTools Network and enable “Preserve log.” Open the one-time create page, type the test sentence orange-lake-7, set reads to 1, and generate the link.
  2. Inspect the create POST. The body should have a ciphertext field. It should not contain orange-lake-7. Note the generated URL: it should look like s.html?id=…#…, with a slice after the hash and only id after the question mark.
  3. Paste the full link into a new tab. The document request line should be …/s.html?id=…. It should not show # or the key after it.
  4. Then inspect the following Fetch / XHR calls. The ciphertext request should carry the id, not the key. Analytics should not take the slice after # or the original test sentence.
  5. The page should decrypt the test sentence. In another tab, paste only the part before the hash. The page should say the link is incomplete, not print the original.
  6. Reload the first, already-read link. You should see burned or expired, not the original. To send again, create a new link. There is no server-side plaintext backup.

MyPassGen’s one-time link works inside that boundary: AES-256-GCM, a 32 KB plaintext cap, the key after #, and no account to create or read. What you should trust is still Network and “strip the hash and it will not decrypt,” not the words “zero knowledge” on the page. The full constraint list is on the security notes page.

What # does not protect

Work email and some messengers unfurl links: a backend GETs the page once to grab a title or a snippet. A preview that fetches HTML and does not run page script never sees the fragment and usually never calls the ciphertext API. A scanner that executes page script can finish a read before the recipient does, and the ciphertext then burns. That is not a protocol bug. It is the cost of “opening the reading page fetches ciphertext in script.” If the other person says the link is already gone, ask whether a preview won the race. To send again, create a new link.

Browser history, crash reports, and some synced accounts keep the full URL. Leave a one-time link in the address bar and you leave the key in local history. After you read it, do not paste the address with # into a ticket template, and do not bookmark it as a “permanent backup.” A lost link cannot be recovered. There is no support reset, and the footer does not publish an inbox that can restore ciphertext.

When you send a longer write-up, the link and the body are a second job. Strip utm_source and click IDs with the parameter table; mask phone numbers and ID numbers in the ticket with which fields to redact before sending. A one-time link answers “the host cannot see the key or the plaintext.” It does not answer “this discussion should not carry a full account number.”

Common mistakes

“The key is in the URL, so the server must see it.” That depends on the slice. After ?, yes. After #, RFC 9110 says the document request and Referer should omit it. Read the request line in Network. Do not argue from instinct.

“If it sits after #, chat history is safe.” Chat apps store the string you pasted. A missing key in the server log does not mean a missing key for everyone in the channel, or for whoever unlocks that phone later.

“A self-destructing link stops screenshots.” It does not. It reduces repeat opens and server-side plaintext. It does not reduce copy or a photo of the screen. Send only a password you can rotate.

“The recipient has to sign up to read it.” On this site, create and read both work without an account. The reading page is public to the recipient. “Opens without signing in” is not “whoever has the link must log in first.”

Where to start

Start with the password you were about to send today. Walk the six steps above with a disposable test sentence first: the create request has no original, the read-page request line has no key after #, stripping the hash fails to decrypt, and a second open shows burned. Then send the real secret.

Generate the real password on this machine with the password generator. Random mode is 6–128 characters, default 16; anything under 8 should be treated as weak. Send the full link. For a more sensitive handoff, split the hash and the path across two channels. Do not paste the same password into chat as a “backup.” After that one send you can already answer the title: the decryption key belongs after # so the HTTP service that hosts ciphertext never sees it. It does not protect chat history or a screenshot.

FAQ

If the key sits after #, can the server really not see it?

Per RFC 9110, the document request’s target URI excludes the fragment. Per MDN, Referer omits it too. Open Network: the read-page request line should not show the key after the hash. If page script reports location.href on its own, that is an implementation leak — look for it in the Fetch list.

Does the recipient need an account to open the link?

No. Create and read both work without an account. The recipient opens the reading page and decrypts. After the configured number of reads, or when the TTL hits, a later open shows burned or expired, not the original.

I copied only the part before the hash. Can I still open it?

You cannot decrypt. Without the key after #, the reading page should say the link is incomplete. Ask the sender for the full URL. A lost or already-burned link cannot be recovered. To send again, create a new one.

Does this keep the chat app from storing a record?

No. Chat apps usually store the whole URL you pasted, key included. The hash only keeps the key off the HTTP service that hosts ciphertext. If you do not want a long-lived channel to hold the credential, do not paste the full link into that channel.