A. Martins Pacheco
← All posts
Aug 2026 · 12 min read

I built a page that spies on you

I built a single HTML file that reads everything a website can learn about a visitor, then pointed it at my own hardened setup. Brave, Do Not Track, a DNS sinkhole for the whole flat — and it still named my GPU, my city and my job.

Then I asked permission first, which rather spoils the surprise. Bear with me.

I was reading about browser fingerprinting for a module at the Swiss Cyber Institute and ran into the usual problem with this topic. Everything written about it is either a research paper full of standard deviations, or a blog post that says "websites can track you!" and then recommends a VPN. Neither one makes you feel anything, and this is a subject where the feeling is the entire point.

So I built the thing instead. One HTML file. No backend, no database, nothing logged. It opens in your browser, reads everything a website is technically capable of reading about you, and prints all of it on the screen. For each item it explains what the value is, how it gets abused in the wild, and whether you can do anything about it.

poc.martinspacheco.ch/what-they-see

It asks you to agree before it reads anything. That isn't me being precious. Under GDPR and ePrivacy, fingerprinting counts as accessing information stored on your device, and that needs consent whether a cookie is involved or not. It would have been a bit rich to write a page about consent theatre and then skip the consent.

I pointed it at myself first

I should have been the hard case. Brave, not Chrome. DNT: 1 set. AdGuard Home filtering DNS for the whole flat. I'm a cyber security student. If anyone was going to walk away from this looking clean, it should have been me.

Here is some of what the page had before I touched anything:

My city, correctly. My internet provider, by name, flagged as a residential line rather than a datacentre. My public IPv4 address, and an IPv6 address alongside it that most people forget they even have. (All four of those are [redacted] here, for reasons that will be obvious by the end of this post — but the page had them, and it had them before I clicked anything.) NVIDIA GeForce RTX 4090. Twenty logical cores, 32 GB of RAM. 1920×1080, and a second monitor. Windows. Brave 151. Audio hardware running at 96 kHz across eight output channels. A Widevine tier of L3, which tells a streaming service to cap me at 720p and tells a fraud engine what kind of machine I'm on. Three text-to-speech voices — David, Mark and Zira — which is the stock Windows English set and would be identical in every browser on this machine.

Nothing there required a prompt. Nothing there required a click. That's just what my browser says when a stranger asks.

Then it got personal.

Three signals, one conclusion

The page probes about 120 fonts by measuring how wide text renders. Mine came back with 56 installed, including the full Microsoft Office set, and — the interesting part — Cascadia Code, Cascadia Mono and JetBrains Mono. Those are programming fonts. Nobody installs JetBrains Mono by accident.

Separately, the section on browser extensions reads any JavaScript object an extension injects into the page. Mine reported React DevTools and Phantom, so: I build React apps and I have a Solana wallet.

Separately again, the section that probes ports on your own machine by timing how fast connections fail found port 3000 answering. That's a Node dev server. I had one running in another window while I was testing.

Three completely independent techniques. Fonts, injected globals, connection timing. None of them knows about the others. All three say the same thing: this person writes React applications for a living, and has a crypto wallet.

That's the whole business model in miniature, on one machine, in about four seconds. No single signal is a big deal. The correlation is.

Also worth noting: DNT: 1, the header I'd conscientiously enabled, is legally meaningless and universally ignored. Its only measurable effect is that most people don't send it, so sending it makes me slightly rarer. I had made myself marginally easier to track by asking not to be tracked.

Cookies were the wrong thing to worry about

Everyone has been trained to think about cookies. We click the banners. We feel vaguely virtuous choosing "reject all". Some of us even clear them occasionally.

A cookie is a file a site leaves on your device. Fingerprinting doesn't leave anything. It works out who you are from how your specific machine behaves — how your graphics driver rounds a floating-point number, how your audio stack processes a sound nobody hears, which fonts your text renders in, how your JavaScript engine phrases an error message.

So deleting cookies does precisely nothing to it.

Section 6 of the page demonstrates this better than I can describe it. I have two exports from my own machine, taken on different days, with all site data cleared in between. The stored visitor ID is gone in the second one — the page cheerfully greets me as a first-time visitor. The composite fingerprint is byte for byte identical: 99781414f5b9… in both.

The page forgot my name and recognised my face anyway.

A few other findings that tend to get the strongest reaction:

Your battery level used to be a tracking cookie. A 2015 paper showed that charge level, charging time and discharging time together produce around 14 million distinguishable states, refreshing every 30 seconds. Two "separate" sessions with a battery at 57% and 143 minutes remaining are the same person. Firefox removed the API over exactly this. Chrome still ships it.

Your GPU introduces itself by name. Not "a GPU". The actual driver string. Mine reads ANGLE (NVIDIA, NVIDIA GeForce RTX 4090 (0x00002684) Direct3D11 vs_5_0 ps_5_0, D3D11). No prompt. Just a function call.

And the one that genuinely bothers me. CSS exposes accessibility settings: prefers-reduced-motion, forced-colors, prefers-contrast, inverted-colors. These are set overwhelmingly by people with vestibular disorders, migraine, photosensitivity or low vision. They're rare, maybe 1–5% of users, and that rarity is exactly what makes them valuable for tracking. The features built to help disabled users are the features that single them out. Nobody planned that. It's just what happened.

The part that should actually worry you

Here I want to be careful, because it would be easy to write the scary version of this post and stop.

One website knowing my graphics card is not a crisis. It's mildly creepy and largely useless on its own.

What makes it matter is that this data isn't held by one site. It's held by an industry that spends serious money stitching it together. Ad networks sync identifiers by bouncing your browser between them inside invisible pixels. Every ad slot you load broadcasts your identifiers and rough location to hundreds of potential bidders, most of whom don't bid and all of whom keep the record. And now that third-party cookies are dying, the replacement is identifiers derived from your email address, which work across sites and across devices, and which you hand over yourself every time you log in somewhere.

Fingerprinting is the glue. Its actual commercial job is repairing the identity graph after you clear your cookies.

The uncomfortable structural point is that nobody in that chain does anything dramatic. Each step is a small, defensible commercial transaction. The aggregate is a surveillance system that no single person designed and no single person is accountable for.

Right, but let's not be ridiculous about it

If a post like this ends with "so don't use the internet", it has failed. Nobody is going to do that and nobody should have to.

Some perspective, then.

Almost all of this is boring commercial surveillance, not somebody hunting you personally. Being uniquely identifiable is not the same as being targeted. The realistic outcome for most people is worse ad targeting, a bit of price discrimination, and their data turning up in a breach dump in four years. That's bad. It's a different kind of bad from what the word "surveillance" makes people picture.

Your threat model matters more than the technology. A journalist protecting a source, someone leaving an abusive relationship, and someone who just doesn't want a mattress company following them for six weeks all need different things. Most of us are the third one. Act accordingly.

And a lot of the defensive work is going well. Cache partitioning, state partitioning, SameSite defaults and site isolation have closed an entire family of cross-site attacks in about five years. The section of my page on history sniffing is mostly a museum piece now, which is a good problem to have.

What actually helps

Ranked by how much each one changed what my page could collect, rather than by how good the marketing is.

Measure Effect Reality check
Tor Browser Very high Slow, some sites block it, and do not customise it
Boring, default configurations High Costs nothing. Puts you in the largest crowd
uBlock Origin High Blocks the requests that carry the data away
Brave High Randomises — "farbles" canvas, audio and WebGL per site, per session, so you look like a different person everywhere. Blocks trackers by default
Firefox, strict ETP High Normalises instead — strong state partitioning plus a known-fingerprinter blocklist. resistFingerprinting goes further and makes its users look like each other
Separate browser profiles High Stops work, personal and shopping being one person
A different email alias per service High Directly attacks the cookie replacement
Permission hygiene High, but only for the prompted things Revoke camera, mic and location when you're done
DNS filtering (AdGuard Home, Pi-hole) Medium Device-wide, but bypassed by DoH and first-party proxying
VPN Low to medium Changed exactly one row of my page and nothing else
Private browsing Low Does not hide you from sites at all
DNT header None Meaningless, and it makes you rarer

That VPN row will annoy some people, so let me be precise. A VPN is genuinely useful on hostile Wi-Fi and genuinely useful for hiding traffic from your ISP. It does nothing about your fingerprint, your fonts, your GPU, your extensions or your mouse movements. The advertising has run roughly a decade ahead of the mechanism.

The single most important idea here: you cannot win by being unusual. The instinct is to install five extensions that each randomise something. That makes you more identifiable, not less, because almost nobody does it and the inconsistencies are detectable. My DNT: 1 is a small example of the same mistake. Anonymity is a crowd problem. You want to look like everyone else, not like nobody else.

What my own defences actually did

I ran the page twice: once with AdGuard Home switched off, once with it back on. The difference is smaller than I'd have liked.

With it off, 4 of the 12 advertising and analytics hosts the page probes were blocked. With it on, 8 of 12. So my DNS sinkhole is doing real work — it doubled the block rate — and a third of the tracking infrastructure still walked straight through. Session-replay and customer-data platforms got through both times.

The timing gave the rest of the game away. Blocked requests failed in about 7 ms, against 194 ms for a control host that loaded normally. Seven milliseconds is far too fast for a network round trip, which means those requests never left the machine — the "blocked" answers were already sitting in a DNS cache.

The sharpest result was one I wasn't expecting the page to catch. It probes use-application-dns.net, which is Mozilla's signalling domain: Firefox looks it up at startup and, if the answer is NXDOMAIN, switches DNS-over-HTTPS off so it keeps using your network resolver. Pi-hole, AdGuard Home and NextDNS all block that domain by default, precisely so Firefox doesn't route around them. On my machine it failed to resolve in 10 ms. My own privacy tool announced its own presence to a web page, in the course of protecting me.

The last one is my favourite. Halfway through testing I'd granted the page camera and microphone access to check those demos worked. Section 1 then started reporting my actual LAN addresses over WebRTC, where the earlier run had shown only obfuscated .local UUIDs. That's documented behaviour: browsers hide your local IPs from WebRTC until an origin holds camera or mic permission, and then they stop. I'd unlocked it myself, four sections earlier, and forgotten.

Read the source

It's one file. Open it in a text editor. Every claim it makes is either code you can read, or is explicitly marked described, not executed — there are several techniques documented in there that I deliberately did not run, because pointing them at a real visitor is rude rather than educational.

That's the standard I'd want from anyone showing me something like this, so it's the standard I held myself to.

Try it → poc.martinspacheco.ch/what-they-see

Bring a colleague who says they have nothing to hide. It's more fun with an audience.


One last thought, and it's the one I actually care about. Every technique on that page began life as a legitimate feature. The Battery API was for saving power. Canvas was for drawing. Client Hints were introduced specifically to reduce fingerprinting. Nobody sat down to build a surveillance apparatus. It accumulated, one reasonable API at a time, because the cost of each addition landed on people who weren't in the room when it was decided.

That's a design lesson, and it generalises well past browsers.