When Stylesheets Become Tracking Devices
Browser fingerprinting is usually framed as a JavaScript problem. Scripts can enumerate installed fonts, read canvas pixels, inspect navigator properties, and combine all of that with the server-side IP address to build a fairly reliable identifier for a visitor. CSS, by contrast, has generally been treated as a benign, non-tracking technology. Oliver Brotchie's CSS tracking research challenges that assumption.
The core trick is deceptively simple. Media queries such as any-pointer evaluate to different values depending on the user's hardware. If a stylesheet requests a unique background-image from a server for each possible value, the server can log which images were fetched and therefore deduce which media queries matched:
.pointer {
background-image: url('/unique-id/pointer=none')
}
@media (any-pointer: coarse) {
.pointer {
background-image: url('/unique-id/pointer=coarse')
}
}
@media (any-pointer: fine) {
.pointer {
background-image: url('/unique-id/pointer=fine')
}
}
A New Draft Makes It Worse
The current media query landscape already enables this technique. Adding prefers-color-scheme to the mix refines the fingerprint with the user's dark mode preference. But Brotchie's real concern is the upcoming CSS user preference media queries draft. He argues that once those land, the method becomes both scalable and considerably more precise:
Not only will the upcoming draft make this method scalable, but it will also increase its precision. Currently, without alternative means, it is hard to conclusively link every request to a specific visitor as the only feasible way to determine their origin, is to group the requests by the IP address of the connection. However, with the new draft, by generating a randomised string and interpolating it into the URL tag for every visitor, we can accurately identify all requests from said visitor.
Stacking the Signals
The technique scales beyond coarse preference checks. Media queries spaced a single pixel apart can be used to pinpoint the exact viewport width of a window, with each boundary triggering a separate background request. Pair that with @supports rules probing for specific CSS feature support — enough to narrow down the browser — and the classic local font detection trick, and you have a serviceable fingerprinting pipeline without a line of JavaScript.
@font-face {
font-family: 'some-font';
src: local(some font), url('/unique-id/some-font');
}
.some-font {
font-family:'some-font';
}
The generated stylesheet required to pull this off is large; Brotchie provides the Sass generator on GitHub. He notes that the size drops considerably once custom properties are permitted inside URLs.
How Much Should We Worry?
There is a reasonable argument for not panicking. JavaScript remains a far more capable fingerprinting vector, and disabling it neutralizes both approaches. CSS has also seen its share of security issues before — visited link history leaks, keylogging via @font-face, and inline-style injection — and browsers have historically shipped mitigations for the worst of them. That said, Brotchie's work is a solid addition to the security literature and deserves scrutiny from those who work on web privacy defenses.



