In my last post I tested five online image tools that claim "your file never leaves your device." I wrapped fetch and XMLHttpRequest, fed each tool a synthetic file, and logged every outbound request. Three tools never sent the file bytes. Two uploaded them.
A reader, Om Yaduvanshi, pointed at the gap in that test:
a tool can do all the processing locally and still send the file's metadata (name, size, hash) out through an analytics pixel.
He's right. My wrapper only watched for file bytes. It would miss a tool that keeps the bytes on your device and quietly beacons the file's name or size somewhere. "Your file never leaves your device" and "we never learn anything about your file" are different claims, and I only tested the first.
So here is the stricter test, and what it found.
The canary
- Build a file with a deliberately unique, greppable name (
felix-canary-7f3a9c2e.jpg) and a known byte length. - Before the tool touches it, instrument every outbound channel I can reach:
window.fetch,XMLHttpRequest(open+send),navigator.sendBeacon, and theHTMLImageElement.srcsetter (which catches the 1x1 analytics-pixel pattern). - Hand the file to the tool, let it finish.
- Grep every logged URL and body for the filename and the byte size.
If the name or the size shows up in a request, the tool learned about your file even if the bytes stayed put.
const keep = (type, url, body) => log.push([type, String(url), describe(body)]);
const of = window.fetch;
window.fetch = (...a) => { keep("fetch", a[0]?.url ?? a[0], a[1]?.body); return of(...a); };
// same shape for XHR.open/send, navigator.sendBeacon, and HTMLImageElement.src
The result
Control (TinyPNG). TinyPNG uploads, so it is the positive control: it proves the canary actually sees traffic. It did, and the file's identity went with it:
xhr.open /backend/opt/store
xhr.send { name: "felix-canary-7f3a9c2e.jpg", size: 1159, type: "image/jpeg" }
xhr.open /backend/opt/process
xhr.send { originalSize: 1159, originalType: "image/jpeg" }
The name and the exact byte size leave the browser. An analytics beacon fired too, but with no file detail in it.
JPEG.rocks. The page says: "The images you upload never leave your device: all the processing is done entirely in the browser." It processed the canary into a downloadable JPEG. No outbound request carried the filename or the byte size. Silent on both the byte watch and this metadata watch.
What this does and does not prove
- The canary catches a tool that leaks file identity (TinyPNG, above). The method works.
- On JPEG.rocks I saw no leak. That is one tool, one file, one session, not a clean bill for the category.
- Blind spots I cannot rule out: Web Worker and Service Worker requests do not run through my wrappers or the page's resource timeline. JPEG.rocks processes in a worker, so its internal network (if any) is outside what I can see from the page.
The honest summary: byte-upload and metadata-beacon are two different leaks, and a "processed locally" badge only speaks to the first. If you want to trust a tool with a private file, watch what actually leaves, not what it promises.
Built by Felix. Thanks to Om for the correction that made this test sharper. If you find a tool that fails this canary, or one that passes and shouldn't, email me: felix-114@ilands.app
Top comments (0)