I built 104 developer tools that never send your data anywhere — and made it provable, not promised
Every developer has pasted something into an online JSON formatter that they shouldn't have. An API response with a customer's email in it. A JWT from a staging environment. A config file with a key that was "only for te
Every developer has pasted something into an online JSON formatter that they shouldn't have. An API response with a customer's email in it. A JWT from a staging environment. A config file with a key that was "only for testing". You know the tool is probably fine. You also know you have no way to check.
I've spent 25+ years developing software on various domains, a lot of it in the kind of environments where pasting a payload into a random website is a policy violation, not a convenience. So I built the tools I wanted to exist: ToolsSonic — 104 developer utilities (JSON, JWT, hashing, regex, Markdown, CSV, HTML, text) that run entirely in your browser, with no account, no upload, no analytics, and no cookies.
That description fits a hundred other sites. What I actually want to write about is the harder part: how do you make "your data never leaves the page" something the user can verify, rather than something they have to take on trust?
The architecture is the privacy policy
The whole site is static HTML, CSS and vanilla JavaScript. There is no backend. Not "a small backend" — none. The tools are JavaScript modules that run in the page, and the page is served from a CDN as files. A JSON formatter that has no server to talk to can't upload your JSON, in the same way a car with no engine can't speed.
This sounds like a limitation. It's the opposite. It removes an entire category of promise I would otherwise have to make and you would have to believe: no "we don't log your input", no "we delete after 24 hours", no privacy policy paragraph that starts with "we may". The privacy policy is the network tab.
Concretely, the tools do not use fetch, XMLHttpRequest, navigator.sendBeacon, WebSockets, or form submission. They don't write your content to localStorage either — session history lives in memory and dies with the tab. The only requests the page makes are for its own static assets on load.
Making it verifiable: the live network counter
"Open DevTools and check" is a fine answer for developers, but I wanted the proof on the page itself, and I wanted it to be honest — meaning it had to come from something the page can't fake.
The browser's Performance API keeps a log of every resource request the page has made. The page can read it; it can't edit it. So each tool has a small line under the input area that does this:
js
function count() {
return performance.getEntriesByType('resource').length;
}
var baseline = null;
input.addEventListener('input', function () {
if (baseline === null) baseline = count(); // snapshot at first keystroke
});
new PerformanceObserver(update).observe({ entryTypes: ['resource'] });
function update() {
var since = count() - baseline;
line.textContent = since === 0
? 'Live proof: 0 network requests since you started typing'
: 'Network requests since your first input: ' + since;
}
It snapshots the request count the moment you first type, then watches. For a tool that's genuinely local, that number stays at zero, and it says so in words. If it ever isn't zero — say a browser extension injects something — it turns amber and tells you to look. It's about 30 lines and it's the most-asked-about feature on the site.
Where "just use JSON.parse" goes wrong
A JSON formatter is the classic first project. Most of them are JSON.parse followed by JSON.stringify(obj, null, 2). That round-trip silently corrupts real-world JSON:
input after JSON.parse → JSON.stringify
{"id": 12345678901234567890} {"id": 12345678901234567168}
{"x": 1e400} {"x": null}
{"a": 1, "a": 2} {"a": 2} — the duplicate is gone, no warning
The first one is the dangerous one. Twitter IDs, Stripe amounts in minor units, database bigints — anything past 2^53 loses precision, and the formatter hands you back a different number with no error. I've watched people debug that for an afternoon.
So the JSON tools on ToolsSonic don't parse to JavaScript values at all. They tokenize to an AST that keeps the original scalar lexemes as strings, then re-emit them verbatim. Big integers survive byte-for-byte, exotic numbers survive, duplicate keys are preserved and counted so the UI can warn you. The engine is 109 lines with no dependencies, and it's MIT-licensed as json-safe if you want it in your own project.
JWT verification without a server — using the browser's own crypto
Most online JWT decoders only decode. Base64-decoding the header and payload is trivial; verifying the signature is the part that matters, and it's the part that would normally mean sending the token and the key to someone's server. That's exactly the thing you should never do.
Browsers ship a real cryptography API: crypto.subtle. It can import an HMAC secret, an RSA public key, or an ECDSA public key, and verify a signature — all locally. So the JWT tool verifies HS256/384/512, RS256/384/512 and ES256/384/512 in the page:
js
const key = await crypto.subtle.importKey(
'raw', new TextEncoder().encode(secret),
{ name: 'HMAC', hash: 'SHA-256' }, false, ['verify']
);
const ok = await crypto.subtle.verify('HMAC', key, signatureBytes, signingInputBytes);
The design decision I'm most attached to: for algorithms it can't verify (none, PS*, EdDSA), it returns an explicit "unsupported" — never a pass, never a fail. A verifier that guesses is worse than no verifier. The result object distinguishes checked: false ("couldn't verify") from valid: false ("verified and rejected"), because those are different facts and conflating them is how people ship bugs. The module is 81 lines, tested against tokens signed with node:crypto, and also MIT: crypto-kit.
The sandbox is the feature: an HTML editor that can't touch the page
The HTML editor renders a live preview of whatever you type. Rendering arbitrary user HTML inside your own page is how you get XSS, so the preview is an — the empty attribute, which means no scripts, no same-origin access, no forms, no navigation. You can opt in to allow-scripts with a checkbox if you want to test JavaScript, and the UI tells you what that changes.</p> <p>This had a consequence I didn't expect. Because the sandbox lacks allow-same-origin, the parent page can't read the iframe's document — so it can't measure the preview's height to auto-size it. The honest fix was a resize handle, not a workaround that only works when scripts are on. The constraint that makes the feature safe also shapes the UI, and I'd rather explain that than quietly weaken the sandbox.</p> <p>Images pasted or dropped into the editor are read with FileReader and embedded as data: URLs, so the file never leaves the browser and the downloaded .html is self-contained. There's a size guard, because base64 makes a 2 MB photo into 2.7 MB of text inside an editable textarea, and that's a bad afternoon too.</p> <p>What it cost</p> <p>Static hosting on a CDN, one domain, one mailbox. That's the entire monthly bill, and it doesn't scale with users because there's nothing to scale. It also means there's no revenue from your data, because there's no data — the site is ad-free and tracker-free by construction, not by policy. If it ever makes money it'll be from things like a self-hosted licence for teams whose firewalls forbid public tools, which is a use case the architecture happens to fit perfectly.</p> <p>Try it, and check it</p> <p><a href="https://toolssonic.com/">toolssonic.com</a>. Open any tool, open the Network tab, paste something, and watch nothing happen. The two libraries above are on GitHub under MIT; issues and PRs welcome. And if you find a tool that does make a request after you type — the counter will tell you before I do — I want to hear about it.</p>
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.