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 example https://api.example.com), and a CONNECT_JOB_SET_SOCKET event 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 %%EOF for 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 %%EOF is 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.