How to prove a website uploaded your file (I tested 29 file tools with Chrome's NetLog)
Many free PDF and image tools now say your file "never leaves your device". Some of them are right. I wanted a way to check that claim properly, not by eyeballing the Network tab, so I built a small test and ran it on 29
Many free PDF and image tools now say your file "never leaves your device". Some of them are right. I wanted a way to check that claim properly, not by eyeballing the Network tab, so I built a small test and ran it on 29 free tools. This post is the method; the results are in a public table at the end.
Why the Network tab is not enough
The DevTools Network panel is a good first look, but it misses things. The Size column shows the response, not what was sent, so a 2 MB upload can look like a 0.2 KB request. Uploads made with fetch(blob) or a PUT to a storage bucket do not show a request body in many tools. And Playwright's request.postDataBuffer(), which I used in my own end-to-end tests, returns nothing for Blob bodies in Chromium, which is exactly how many sites upload.
Chrome's NetLog sees everything
Chrome can write a full log of its network stack:
chromium --log-net-log=run.json --net-log-capture-mode=Everything --disable-quic
In "Everything" mode the log includes, for every request, an UPLOAD_DATA_STREAM_INIT event with the total body size, and SSL_SOCKET_BYTES_SENT events with the decrypted bytes written to each TLS socket. Two details matter:
- Turn HTTP/3 off (
--disable-quic). Over QUIC the payload is inside encrypted UDP packets and you lose the plaintext. - To know where bytes went, map each socket to its host: the connect job that created the socket carries a
group_id(for examplehttps://api.example.com), and aCONNECT_JOB_SET_SOCKETevent links the job to the socket.
With Playwright this is just launch arguments:
const browser = await chromium.launch({
args: ['--log-net-log=run.json', '--net-log-capture-mode=Everything', '--disable-quic'],
});
Make the file recognisable
Size alone can mislead: session-recording scripts sent beacons of around 50 to 90 KB in my runs, bigger than a small test PDF. So every test file carries a random marker:
- in the file name (
report-<token>-a.pdf), - in the contents: a JPEG COM segment for images, and a PDF comment appended after
%%EOFfor PDFs. (pdf-lib writes Info dictionary strings as UTF-16 hex, so a marker in the Title would not appear as plain text. A comment after%%EOFis ignored by readers and does not move any xref offsets.)
A site "uploaded the file" if the marker shows up in bytes sent to a host, or file-sized data went to the site's own servers or file storage. A site "kept it on the device" only if neither happened and the job actually finished.
Controls and repeats
Each site ran twice in a fresh profile, and a result counted only if both runs agreed. As a control I ran a tool I know uploads (Drible's own Compress PDF, which uses a server); the harness flagged it every time. One one-off observation (a file name appearing in an error-tracking payload) did not repeat in four more runs, so it stayed out of the results. Repeats matter.
What it found
- All 14 sites that say files stay on your device kept that promise.
- 10 of 24 other sites uploaded the file, most of them saying so on the page.
- 9 of those 10 started uploading 0.05 to 2.5 seconds after the file was chosen, before any button was pressed.
- 7 tool pages loaded session recording (Microsoft Clarity or Hotjar).
Full table, method and data (CSV and JSON, CC BY 4.0): https://drible.co/research/file-upload-test
Disclosure: I build Drible, one of the tools in the table. It is measured with the same harness, including a server tool that uploads.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.