How a privacy test keeps no data

A privacy test is a tool that shows you what websites can learn about your browser — canvas and GPU signals, fonts, screen, timezone, language, network hints and the quiet identifiers that let a site recognise you on return — and that creates a credibility problem: a tool that demonstrates surveillance techniques is itself part of the product it is judged by, so its own data practices cannot be an afterthought or a policy page. The design rule behind our Browser Fingerprint Test is simple enough to state in one sentence: if the browser can read it, the server never sees it. This article explains what that rule costs, where it bends, and where it stops. If you want the inventory of signals first, start with what websites know about you; this piece is about the plumbing underneath the test, not the signals themselves.

Client-side first

Every signal the browser itself offers is read and rendered locally, on your machine, by the page code running in your tab. Canvas rendering, WebGL renderer strings, audio context properties, installed font probes, screen and window geometry, colour depth, touch support, storage estimates, media queries for contrast and motion preferences — all of it is computed where it is discovered and drawn into the page where you can see it. The page code is the only thing that ever holds the full picture. No script collects the panel into a payload, no beacon carries it home, and no analytics event summarises it, because the moment a complete fingerprint leaves the browser, the test has become the thing it warns about.

That ordering — local first, server last, and server only for the few jobs a browser genuinely cannot do alone — is not a feature of the architecture; it is the architecture. Most privacy tools are built the other way round: collect everything centrally, then promise to delete it, anonymise it or hold it briefly. Those promises may be kept, but they are promises, and a reader has no way to audit a deletion. A design that never transmits the data in the first place replaces a promise with a fact about the code path, and facts about code paths can be checked in a network tab, which is exactly the standard a privacy tool should invite.

The cost is real and accepted by design. There is no cross-device anything: no account that follows you, no history that syncs, no “your fingerprint on your other laptop” comparison, because all of those require the server to hold something per person. Comparisons are limited to what your own browser remembers locally and to the anonymous daily counters described below. When a feature request would need the full fingerprint on the server, the answer is not a more careful retention policy — it is no. The constraint is the product.

Three small endpoints, three different jobs

Three jobs genuinely cannot be done from inside the page, and each gets its own small endpoint with its own narrow job. The first, /api/check/whoami, returns only what any server already sees in the request that fetched the page: your network address as it arrived, the headers your browser sent with it, and the coarse geography attached to that address. Showing you those values adds no new exposure, because every server you visit receives them whether it displays them or not; the endpoint simply turns a one-way disclosure into a visible one, and keeps none of it beyond the response.

The second, /api/check/etag, exists only to demonstrate cache-based recognition, explained in the next section: it hands out a random cache identifier and keeps nothing at all — no table of issued values, no record of who asked. The third is the counter endpoint: it accepts a handful of coarse values from a finished run and returns running totals, so the page can say how common your configuration is without anyone storing your visit. Each endpoint is rate-limited like the site’s other checkers, because small and honest is not the same as unlimited, and a demonstration endpoint that can be hammered is an abuse surface wearing a good intention.

The page is also explicit about the outside services it contacts directly from your browser, because those services see your address too and honesty about them is part of the design. The test reads Cloudflare trace endpoints, queries a DNS echo service to reveal which resolver answers for you, and opens connections to two public STUN servers to show what a WebRTC-style lookup can learn. Each is named on the page itself, next to the result it produces — the same disclosure discipline we apply to the leak checks in VPN leaks and disguise checks. A privacy test that quietly phoned third parties would fail its own lesson.

The ETag trick, honestly explained

An ETag is an ordinary piece of HTTP caching: a server attaches a validator to a response, the browser stores it with the cached copy, and on the next visit the browser sends the validator back so the server can answer “not modified” and save the transfer. The trick is that the validator does not have to describe the content. A server can issue a unique validator to each visitor, and the browser will faithfully return that unique value on every revalidation — at which point the server recognises the browser exactly as reliably as with a cookie, without ever setting one. Cache validation doubles as an identifier because the browser is doing its job correctly.

That is why clearing cookies does not clear it: cookies and cache are separate stores, emptied by separate controls, on separate schedules. It is also why a private or incognito window breaks the trick — a private window runs with its own separate cache that is discarded when the window closes, so the validator dies with the session. Our endpoint demonstrates the round-trip and stores nothing: recognition is proven by the 304 response itself, by the server seeing its own random value come back, not by any record kept between visits. The demonstration ends the moment the page closes, which is precisely the property the real-world trackers using this technique do not offer.

There is one honest wrinkle worth naming: the edge cache. If responses passed through a shared CDN cache, the first visitor’s identifier could be served to everyone, and the demonstration would show a crowd sharing one ID instead of each browser holding its own. So this endpoint opts out of edge caching entirely — every request reaches the origin, every visitor receives a fresh random value, and no shared cache ever holds one. It is a small configuration line, but it is the difference between a demonstration that proves the mechanism and one that accidentally proves the opposite.

Counters instead of logs

One question does need the server: “how unusual am I?” A page that can say your GPU appeared in N of M runs today is far more instructive than one that can only list signals, because rarity is what makes a fingerprint identifying. But answering it the obvious way — log every visit, then count — builds exactly the database the tool exists to warn against. So the test keeps counters instead of logs: one number per value per day. The operating system, the browser family, the GPU string, the timezone, the first language, the screen size class, the first eight characters of fingerprint identifiers, the outcome of each individual check, and a run-duration bucket — each contributes a tick to a daily total, and nothing else is kept.

The boundaries matter more than the mechanism. The full fingerprint is never sent, let alone stored; your address is never sent to the counter at all. A counter cannot be queried backwards into a person, because there is no per-visitor row to find — aggregation happens at the moment of counting, not later as a reporting step over stored records. This is the same reason the totals reset by day: a counter that accumulated forever, sliced finely enough, would quietly become a fingerprint database by accumulation, and the design refuses that drift in advance rather than apologising for it afterwards.

Two further choices protect people who never read this article. Browsers sending the Global Privacy Control signal are never counted at all — the signal means “do not track me”, and the most respectful reading of it is to exclude those runs entirely rather than count them carefully. Everyone else gets a visible opt-out on the page, honoured before any counting happens. And a run that does not finish within 45 seconds counts once, as “stuck”, in its own bucket: slowness and breakage stay visible to us as operators, as a single aggregate number, without following any individual visitor through a failing run.

What we chose not to build

The most instructive part of the design is the list of things left out. There is no connection-level probe: no TLS fingerprinting, no JA3-style reading of your handshake. The honest reason is architectural — TLS for this site is terminated at the CDN standing in front of it, so a handshake reading taken at our origin would describe the CDN’s negotiation and settings far more than your browser’s. Publishing it as “your TLS fingerprint” would be theatre: a plausible-looking value with someone else’s name on it. A privacy tool that prints one confident wrong reading teaches its users to trust confident wrong readings everywhere else, which is worse than an empty panel.

The same discipline shaped the rest of the omissions. The counters are coarse because fine-grained counters become a fingerprint database by accumulation, one innocent dimension at a time. The last-visit memory — “since your last run, these values changed” — lives only in your own browser’s storage, on your machine, and is never transmitted; the moment it synced, it would be a history, and a history is a record. Features that would have been genuinely useful were cut for the same reason the endpoints were kept small: usefulness is not a defence when the data cost is the whole point of the tool.

Finally, the residual honesty note, because no design article should end on its own compliments. This design minimises data; it cannot make the readings themselves less identifying. Your GPU, your fonts, your screen and your timezone combine into something close to unique whether or not anyone stores them — that is the finding the tool exists to show you. Keeping no data protects you from us. It does not protect you from the technique, and pretending otherwise would be the one truly dishonest feature we could ship.

Sources and further reading

Frequently asked questions

Can I verify that you keep nothing?

Largely, yes — and you should. Open your browser’s network tab, run the test, and watch what leaves your machine: local readings render without any matching upload, and the only requests are the three named endpoints and the named outside services, carrying the narrow values this article describes. A claim of “no logs” on any site ultimately rests on server behaviour you cannot see, which is exactly why the design minimises what is sent: what never leaves your browser cannot be logged, sold or leaked by us in the first place.

Why count anything at all?

Because rarity is the lesson. A list of your signals tells you what a site can read; “this GPU appeared in 12 of 4,000 runs today” tells you how identifying that signal is, which is the difference between trivia and risk. Counting was kept because that context matters, and it was shaped — coarse values, daily totals, aggregation at the moment of counting — so that the context never requires a record of you.

Does the opt-out delete my past counts — and what is Global Privacy Control?

There is nothing per-visitor to delete. Counts are aggregates — one number per value per day — with no row, identifier or visit record attached to you, so opting out stops any future counting of your runs but cannot subtract a tick that was never linked to you in the first place; that is the reason the system was built as counters instead of logs. Global Privacy Control (GPC) goes further: it is a standard signal — a browser setting or extension, described at globalprivacycontrol.org, that tells every site “do not sell or share my personal information”. Several browsers ship it as a toggle. This test honours it in the strongest way: browsers sending GPC are never counted at all, no opt-out click required.