Search for “encrypt file in browser” or “client-side encryption online” and the results almost always say the same thing: computation stays local, the file is not uploaded, the key never leaves the device. Those sentences are comforting. They are not proof. A page can print them while a script still fetches the passphrase in the form, a slice of the file, or the key sitting in the address bar. What you need to verify is not the copy. It is whether this one action sent plaintext out of the browser.

This article is not a product picker, and it is not a tour of a tool page. The question is narrower: when a site claims it encrypts in the browser, what can you see right now in DevTools and the address bar? MyPassGen’s file encryption and one-time links both use AES-256-GCM through the Web Crypto API, and every tool opens without an account. The same check applies to them. You do not have to take “we do not store this” on faith.

Slogans cannot be checked. Traffic can

“Client-side encryption” is a worn phrase. One site POSTs the file to its own server, wraps it in AES there, and hands back a download link — and still calls that “online encryption.” Another site calls crypto.subtle.encrypt in the tab, writes a .lock or .enc file, and never sends a business upload. Marketing pages can use one sentence for both. The Network panel cannot.

The browser will not audit the slogan. It will list the document request, scripts, images, and XHR / Fetch calls this click produced. You can see whether the request URL holds plaintext, whether the body holds a passphrase, and whether an analytics url includes the key after #. If those strings are absent, “does not leave this machine by default” is still standing. If they are present, the slogan is already dead.

You do not have to read the source. You need one repeatable pass: open the panel, do one concrete thing (generate a password, check a disposable passphrase, encrypt a small file, or create a one-time link), then open each new request. First split what “local” names. Then separate the hash from the query. Then run the steps.

What “local” actually names

The Web Crypto API is the browser’s cryptography surface. crypto.subtle hashes, derives, encrypts, and decrypts. The W3C use cases include a lawful design where the user picks a key in the browser, encrypts a document, then uploads the encrypted bytes to a provider. The API guarantees that the math can run on this device. It does not forbid sending ciphertext afterward, and it does not stop an author from uploading first and encrypting later.

Web Crypto also requires a secure context: HTTPS in production (localhost is the usual exception). That is a precondition, not a talking point. If a page on plain HTTP claims it “uses Web Crypto,” look at the lock in the address bar before you argue about algorithms.

What AES-256-GCM on this device actually means

MyPassGen’s first release uses AES-256-GCM only. Per MDN AesGcmParams and NIST SP 800-38D, which it cites, GCM is authenticated encryption: if the ciphertext is altered, decrypt fails. You do not get a garbled body that looks like a document. The recommended IV is 96 bits — 12 bytes — and you must use a fresh IV for every encryption under the same key. The IV is not a secret; it can travel with the ciphertext. The authentication tag defaults to 128 bits. Those numbers are checkable in an implementation. They are not slogans to memorize.

File encryption also derives a key from a passphrase. This site’s file box uses PBKDF2 with 100,000 iterations, SHA-256, and a 16-byte salt, then a 256-bit AES-GCM key. One file can be up to 5 GB. Output is .lock or .enc. If the passphrase is POSTed alongside the file, the iteration count does not save you. The first object to inspect is still the traffic, not the KDF.

Three kinds of “encrypt in the browser”

Laid out, the flows split into at least three. First: the tab only picks a file; the bytes go to a server; encryption happens on the other side. Second: the tab encrypts first, then uploads ciphertext only, and the key travels on another channel (for example the # in the URL). Third: the result downloads to this machine, and no business request carries a file body. All three can be sold as “encryption on a web page.” Only the last two get to claim that plaintext is not uploaded by default. Your job is to decide which class this page is, using Network.

Name the object before you click Start

For password generation, a strength check, link cleaning, or file encryption, the expectation is “plaintext did not leave as business data.” A one-time link is different: you may see a ciphertext field, but you should not see the original sentence, and you should not see the key after # on the request line. Mix those two expectations and a normal ciphertext POST looks like a leak.

Hash and query are not the same object

A URL splits into scheme, host, path, query (after ?), and fragment (after #). RFC 3986 §3.5 calls the part after # a fragment: it names a secondary position the client interprets after the primary resource arrives. MDN’s note on the URI fragment is plainer: when the browser requests that URI, the fragment is not sent to the server. The client handles it after the resource lands.

The query string is the opposite. Open an https link and the name=value pairs after ? enter the request line and the far-end access logs. The previous piece, which URL parameters to strip when you share a link, already treated utm_source and id= as query names. Write a key as ?key= and the server, the reverse proxy, and CDN logs can all see it. Write it after # and the default request line does not include that slice.

Referer also drops the fragment. 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 key after # and the machine that hosts the ciphertext never receives it over HTTP” is protocol behavior. It is not a vendor promise.

A one-time link on this site looks like s.html?id={id}#{key}: the query holds only the ciphertext id; the key lives only after the hash. On create, the tab builds a 32-byte random key, runs AES-256-GCM with a 12-byte IV, then POSTs ciphertext. On read, the script takes the key from location.hash and decrypts on this device. Plaintext is capped at 32 KB. What you check: the create JSON should have a ciphertext field and should not have the original sentence; the document request on the read page should show id= and should not show the slice after #.

Check it in the Network panel

Chrome, Edge, Firefox, and Safari all ship a network panel. Names differ a little. The steps do not. You do not need an extension, and you do not need to hand the file to a third-party “scanner” — uploading the full URL or the file again only widens the exposure.

  1. Open DevTools and switch to Network. Enable “Preserve log” so a navigation does not wipe the list.
  2. Filter to Fetch / XHR (or “XHR”). Static assets can wait. Business uploads almost always use this class of request.
  3. Do the action you came to check: generate a password, type a disposable test passphrase, paste a URL that still carries UTM tags, pick a small file to encrypt, or write a throwaway secret and create a one-time link.
  4. Open each new request. Read the Request URL: the document and API addresses should not contain the plaintext you just typed, and they should not contain # plus the key.
  5. Open Payload / Request. JSON or form fields should not hold the original text, a passphrase, the password under test, or the raw file bytes. A one-time create may show a ciphertext field. That field should look like random binary in Base64, not the sentence you typed.
  6. Scan analytics or collect requests (names often include matomo, collect, or g/collect). See whether the reported page URL or a custom parameter sent location.href including the hash.

File encryption has a harder check: disconnect or turn on airplane mode, then encrypt a small file. If the math is supposed to stay on this device, the .lock / .enc download should still succeed. A one-time link cannot pass that test — it has to park ciphertext on a server, so create failing offline is expected, not a gotcha. Treat “zero requests” as the only passing grade and you will fail a zero-knowledge burn link by mistake.

A strength-check page may fetch a static weak-password list (this site uses leaked-top10k.txt). That is a word list, not your input. In Network you may see the list file. You should not see the password from the input box in the query or the body. That is not a full-web HIBP lookup. HIBP-style checks send a hash or a password fragment to an outside API.

What you are doing Allowed in Network Must not appear
Generate a random password The page’s own scripts and styles The result or the chosen character set POSTed
Check a passphrase locally A static weak-password list The password in the input box
Clean a link or redact text No business request carrying the original The full URL, an ID number, or a raw email
Create a one-time link Ciphertext, an id, expiry, and read count Plaintext; the # key on the request line
Encrypt a file No business upload of a file body File bytes, the passphrase

MyPassGen is built to that table. Random passwords are 6–128 characters (default 16; below 8 it warns that the result is weak). Strength checks use a local list, not a web-wide lookup. Cleaning and file encrypt/decrypt do not upload the original as business data by default. A one-time link stores ciphertext only. Every tool opens without signup. The “must not appear” column is something you can tick in the panel yourself. You do not have to accept a brand claim first.

Ciphertext leaving is not plaintext leaving

Zero-knowledge sharing and “fully offline” get mashed into one sentence. The file box is the second kind: the result lands in the download folder, and the server has no business copy of that file. A one-time link is the first kind: the other side must be able to fetch a blob of ciphertext so the recipient can decrypt in their own browser. The server seeing ciphertext and not seeing the key is the design, not an accident.

Split the leak test. On create, a long Base64 string in the POST body is normal. If that same field reads as the “test API key” you just typed, plaintext left. The document request on the read page should be s.html?id=…. Under the spec, the Request URL listed in Network does not carry #. Open Headers on that request and confirm the request line and Referer both omit the key.

GCM’s authentication tag makes “flip a few bytes, then decrypt anyway” hard: if the tag does not match, decrypt fails outright. That protects integrity and authenticity. It does not mean the link will never be forwarded. Anyone who has the full URL — including the key after the hash — can decrypt until the read count is spent. Network checks whether the server got the key. Chat logs, mail, and screenshots are a different risk. The next section treats those alone.

Do not paste a full one-time link into a “scanner” that uploads the original

The key after # is invisible to the HTTP server. It is fully visible to the next site you paste into. Verify in your own DevTools. Do not hand s.html?id=…#… to an unknown scan page.

Leaks the spec does not cover

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 — that is an implementation bug, not a protocol hole. Step six exists for that: if analytics reports location.href as the page URL, the slice after the hash enters the stats log. The correct report is a path, or a URL with the hash dropped. “HTTP will not send it” is not a substitute.

Browser history, synced tabs, and some crash reports keep the full URL. Leave a one-time link in the address bar and you leave the key in local history. Share it on a one-shot channel, and after reading, do not paste the address with # into a ticket template. Referer strips the fragment; query parameters can still ride to a third party. Never move the key into ?key=.

Screens, clipboards, and group-chat logs sit outside the protocol. A coworker who screenshots the full link into chat still did not give the server the key — and everyone else in the thread now has it. Local encryption answers “where did the math run, and who cannot see plaintext by default.” It does not answer “will the person who got the full URL forward it.” The same split applies to a file passphrase: the .lock can go to a drive; the passphrase has to travel on another channel. A short secret belongs on a one-time link, not in the same email body as the passphrase.

Common mistakes

“It fails offline, so it must be uploading plaintext.” A one-time link has to park ciphertext. Offline failure only shows that it depends on one ciphertext transfer. Read what that transfer contains. Do not stop at “there was a request.”

“HTTPS already encrypts the path, so local encryption is redundant.” HTTPS covers eavesdroppers on the wire. The server is the TLS endpoint. It can still read the request body and the query. Local encryption is the layer that keeps the server — or a midway analytics hop — from seeing plaintext by default. It stacks with TLS. The jobs are different.

“The page mentions Web Crypto, so plaintext is not uploaded.” Web Crypto only supplies primitives. Encrypt first, then upload ciphertext, and POST a FormData first, then encrypt on the server, can both decorate the footer with the same API name. The payload in the panel outranks the slogan.

“I see no request to the product host, so I am safe.” Extensions, system proxies, and some enterprise gateways never draw themselves in the page Network list. The panel can falsify a “nothing is uploaded” ad. It cannot prove there is no second channel in the world. For choosing an online tool day to day, falsification is enough: see plaintext, stop.

Where to start

Pick one disposable action you already need today. File encryption is the easiest contrast: choose a screenshot with nothing sensitive on it, type a temporary passphrase, open Network, click encrypt, confirm there is no file-body POST, then download .lock or .enc. When you send that picture to someone else, the ciphertext goes to a drive; the passphrase goes in another message. Keep a single file at or under 5 GB.

If you are sending a one-line password or a recovery code, use a one-time link instead: write a test sentence, generate the link, confirm the create request holds ciphertext only; open the read page in a private window and check that the document request line has only id=. Do not treat the read page as an indexable content page — it is a one-shot ciphertext scene. Link cleaning still follows the previous article’s table: strip UTM tags and click IDs on this device.

After one pass you can already answer the title: whether browser encryption is real is not a slogan. It is whether this traffic contained plaintext, a passphrase, or the key after the hash. AES-256-GCM, Web Crypto, and tools that open without an account are constraints you can check in parallel. They are not promises you have to register to hear.

FAQ

Why can file encryption work offline when a one-time link cannot?

File encryption finishes as a local download. A one-time link has to let the recipient fetch ciphertext later, so create POSTs ciphertext. Both can finish AES-256-GCM in the browser. What is allowed to leave is different: the file box should have no file body; the one-time create may send ciphertext and must not send plaintext or the key.

Does the server really never receive the key after #?

Under ordinary URI and HTTP implementations, the document request and Referer omit the fragment. You can see that on the document request in Network. If a script reads location.hash and reports it, that is the page’s own behavior — look for it in the Fetch list.

Is it easier to put the key in the query string?

Easier, and it lands in server logs. ?id= may locate ciphertext. ?key= hands decrypt capability to the host and every log along the path. When the recipient must decrypt and the host must not, the key goes after #.

Do I need an account to run this check? Will analytics take the original?

No account. If analytics records a page type or a path with the hash stripped, it does not take the input-box contents. If it reports the full href, the key after # can enter the stats log. Reading the analytics request query in Network is more direct than reading “we care about privacy.”