TL;DR: The third-party cookie was a container, never the tracker. Regulators and browsers attacked the container; identification moved into the fingerprint, the connection, the cache and the site's own subdomains. A clean profile left behind zero cookies and was still unique — and only one of the mechanisms in this article needs JavaScript at all. "We do not use cookies" answers a question nobody serious was asking.
The scenario
I audit websites for a living, so I open a lot of them the way a sceptical visitor would: fresh profile, no history, no consent, one page load. That setup is the only honest test of what a site does to someone who has agreed to nothing.
On a good day the cookie jar comes back empty and the site congratulates itself in the privacy notice. On the same page load, a fingerprint computed from screen, fonts and GPU has already made me one-of-one; the TLS ClientHello my client sent has already been hashed by a fingerprinting service upstream; and a subdomain of the site has quietly answered from an advertising network's address. None of that needed a cookie, a click, or a dialog.
This article is about that gap. I already wrote about what a browser hands over — the live banner under this site's navigation shows it to every visitor. This is the other half: what the receiving end does with it, in the post-cookie world we actually live in.
I keep the technical claims reproducible and the lab local. No real visitor, no third-party service is probed in a way that identifies anyone.
The pattern
The mental model most people carry — tracking = cookies — was always a simplification, and in 2026 it is actively wrong. Here are the places identity now lives instead.
The fingerprint, computed on every visit
A fingerprint is not a file. It is a recomputation: a script reads values the platform exposes anyway — screen and pixel ratio, time zone and language, CPU cores and estimated memory, the WebGL renderer string, the set of installed fonts, the exact pixels a canvas produces — and hashes the combination.
The arithmetic is unforgiving. Around 33 bits of entropy are enough to single out one person in the world; a single modern machine routinely offers 20 to 30 bits of usable uniqueness from a handful of values. Nothing is persisted, because nothing needs to be: the value is derived fresh from the same hardware every time. There is no cookie to decline, and "clear your cookies" removes none of it.
The connection is an identifier before JavaScript exists
Long before the page runs, the server has already seen the client's TLS handshake — the ordered list of ciphers, elliptic curves and extensions — and its HTTP/2 settings frame. That pattern is stable per client implementation and is captured as JA3/JA4 (TLS) and the Akamai fingerprint (HTTP/2). Header order and casing add a little more.
This matters because it is server-side and passive. No script, no storage, no consent dialog. Even a bare curl is a signature. A tracker that fingerprints at the handshake has identified the connection before deciding whether to bother with the page at all.
Client hints re-open a door that was politely closed
User-Agent strings were reduced because they leaked too much. The replacement, Client Hints, lets a site ask for the detail back with an Accept-CH header: full version list, platform version, device model, and more. The browser then sends those on subsequent requests — no prompt, no opt-in that a visitor would recognise.
So the entropy did not disappear; it became opt-in for the site, not for the user. A page that wants the old precision simply requests it, and a Chromium browser obliges. (Support differs by engine — which, conveniently, is itself a fingerprinting signal.)
State hides in caches, not cookies
"We do not use cookies" is a statement about one storage API. Identity also lives in localStorage, sessionStorage, IndexedDB and the Cache API — all same-origin, all persistent, none of them a cookie, none of them requiring a cookie banner. The HTTP cache can do the same trick: an ETag or a cache-timing probe turns "do I still have this resource?" into a stable bit of state.
The container changed. The storage did not go anywhere.
The tracker that wears your own domain
Ad blockers block third-party hosts by name. The counter was elegant: give the tracker a CNAME on a subdomain of the site (metrics.example.com → vendor.tracker.net). The browser now sees a first-party host, blockers leave it alone, and the visitor's consent — given to the website — silently covers a vendor. Analysing where a site's subresources actually terminate is now a security-review step, not an ad-blocker feature.
Why the cookie framing persisted
Because it was convenient for everyone. For sites, a cookie notice looks like consent. For regulators, the cookie is a visible, legible object. For vendors, cookie-based tracking was easy to explain and easy to bill. The fingerprint, the handshake and the cache have none of those properties — which is exactly why they are where the tracking went.
Findings
| Severity | Mechanism | Why it matters |
|---|---|---|
| HIGH | Server-side TLS/HTTP2 fingerprinting (JA3/JA4) | Identifies at the connection layer, before any script or consent — invisible to cookie controls |
| HIGH | First-party subdomain pointing at a third party (CNAME cloaking) | Defeats hostname blocking; moves a vendor inside the site's consent |
| MEDIUM | JavaScript device fingerprinting (canvas, WebGL, fonts, hardware) | Re-identifies with no storage and no cookie; recomputed per visit |
| MEDIUM | Client Hints (Accept-CH) | Re-exposes the user-agent detail that was deliberately reduced |
| MEDIUM | Non-cookie storage (localStorage, IndexedDB, Cache API, ETag) | Persistent identifiers outside the scope of cookie consent |
| LOW | Reduced user-agent string treated as privacy | Version reduction without disabling hints is cosmetic |
| INFO | "We do not use cookies" claims | Technically true, materially misleading |
The attack path
- Fingerprint passively. Hash the handshake before the page loads. Cheap, server-side, and outside every cookie rule a visitor has ever been shown.
- Enrich in-page. Add the JavaScript census — canvas, fonts, GPU — to raise confidence from "a client" to "this machine".
- Persist without permission. Use a cache entry or an
ETagso a return visit is recognised even after a cookie wipe. - Absorb the vendor. Point a first-party subdomain at the ad network, so blocking tools and the site's own consent both look satisfied.
- Profile and monetise. Combine the above into an audience, sell it, and let the visitor keep clicking "reject all" — which, for none of these mechanisms, changes anything.
Reproducing it in the lab
I ran a clean browser profile against a controlled stack and captured, in order: the cookie jar, the third-party requests, the in-page census and the handshake signature.
| Scenario | Measured result |
|---|---|
| Fresh profile, no interaction | 0 cookies, 0 third-party hosts — and a unique fingerprint |
| Same profile, cookies cleared and reloaded | fingerprint unchanged (hardware did not change) |
| Scripts disabled | fingerprint gone; handshake signature still present |
| Cookie-wipe only | nothing changes for the server-side and fingerprint layers |
| Hardened browser (canvas/WebGL resistance) | device signal collapsed toward a generic bucket |
The last row is the only one that moves the needle, and it is a browser change, not a click. That is the uncomfortable summary: consent toggles are the wrong control for most of what identifies you.
How to detect it in your own estate
- Assume the connection is observed. Treat JA3/JA4 and HTTP/2 settings as an identifier you neither control nor hide by policy — audit what your edge logs and with whom it is shared.
- Inventory subresources by where they terminate. Follow every CNAME. A first-party
metrics.host that resolves to a vendor is the finding, not a footnote. - Read your own
Accept-CH. If you ask for full versions and platform versions, know that you have re-opened entropy deliberately. - Fresh-profile test per release. Watch cookies and storage and third-party hosts on first paint — not just the cookie banner.
- Ask what you actually need. Most analytics answer their question with far less entropy than a full device census.
How to fix it (in this order)
- P0 — Stop requesting entropy. Drop
Accept-CHrequests you do not need; do not fingerprint at the edge. - P0 — Fix the vendor boundary. No third-party subresources, no CNAME-to-vendor subdomains; if a service must run, run it first-party under your own contract and disclose it.
- P1 — Choose privacy-preserving measurement. First-party, cookieless analytics that cannot re-identify, documented in the privacy notice.
- P1 — Send a strict
Referrer-Policyand cut metadata leakage to everyone downstream. - P2 — Make it visible. Show the visitor what the page can read about them, and be specific about what leaves the browser. Transparency does not stop a determined tracker — it stops the quiet ones who assumed nobody checks.
Once an attacker gains access, they can perform the following actions:
The takeaway
The cookie was never the tracking; it was the receipt. The tracking is the recomputation of who you are from a machine that answers the same way every time — plus a connection that identifies itself before you can be asked, and a vendor hiding behind your own domain. Clearing cookies is hygiene. Understanding what is being derived, and refusing to derive it, is the actual defence.
If you are curious what your own browser gives away on this site, the banner under the navigation is the live version of exactly this article.