field note / 2026 / chrome + v8 A browser fingerprinting lab bench with floating-point test printouts, Chrome version notes, OS comparison tables, oscilloscope traces, and three device consoles under low engineering-room light.

field dossier

Chrome 148 Made Browser Math a Fingerprint

Chrome 148 quietly turned Math.tanh into an OS fingerprint. One library routing change moved a browser privacy boundary down into glibc, Apple libsystem_m, Windows UCRT, CSS trig, and Web Audio DSP.

Chrome 148 turned one line of browser math into a quiet OS confession. Math.tanh(0.8) now returns different final bits on Linux, macOS, and Windows because V8 stopped using its bundled fdlibm-derived implementation and delegated the call to the host C++ std::tanh. A page can ask for a hyperbolic tangent, compare the last digit against a lookup table, and catch a browser whose user agent says Mac while its math smells like glibc.

This sits beside earlier pr0xy.dev browser-control threads on Chrome making email verification into browser plumbing and the browser permission prompt becoming an HTML element. Those pieces followed identity and sensor gates moving into browser UI. This one follows the opposite direction: a privacy boundary slipping downward into standard libraries, CPU behavior, and DSP paths.

The primary artifact is blunt. V8 commit c1486295ae5bcb0f8fb078b7cf921802ccd75eaf is titled [math] Replace custom tanh with std::tanh. The commit message says V8’s custom ieee754::tanh was an fdlibm port, then removes it because modern C++ standard library implementations are optimized and produce correct results across platforms. Chrome 148 shipped on May 5, 2026, according to the official Chrome 148 release notes. Scrapfly’s July 12 writeup measured the consequence across Chrome 148, 149, and 150: the same JavaScript call now reads different OS math libraries.

The cute demo is ugly enough to matter:

Math.tanh(0.8)
// Linux Chrome:   0.6640367702678491
// macOS Chrome:   0.664036770267849
// Windows Chrome: 0.6640367702678489

Those values differ by one or two ULPs, units in the last place. Human eyes treat that as noise. Fingerprinters treat it as a cheap binary signal. If the browser claims macOS and returns the Linux value, the identity stack contradicted itself before canvas, WebGL, fonts, permissions, or behavior models even enter the room.

The reason this works is boring in the best way. IEEE 754 fixes how a floating-point number is represented. It does not force every sin, cos, exp, or tanh implementation to round the same way. glibc, Apple libsystem_m, and Windows UCRT make different speed, approximation, coefficient, and table choices. Most inputs agree. Some split. The split inputs become probes.

Scrapfly’s useful contribution is the routing map. V8 still bundles most JavaScript Math.* behavior through llvm-libc or older glibc-derived routines, so Math.sin, Math.cos, Math.exp, and friends remain stable across operating systems. Math.tanh is the weird new exception. CSS math follows a different path: Blink’s CSS trig functions call host libm, so CSS sin(), cos(), tan(), asin(), acos(), atan(), and atan2() can leak. Web Audio adds another layer because Chrome on macOS uses Apple Accelerate for some FFT and vector DSP paths while scalar compressor math still touches libsystem_m.

That turns the browser into a stack of mathematical accents. JavaScript says one thing. CSS says another. Web Audio can carry both OS and architecture signals. A spoofing system that patches only the obvious surface creates a new inconsistency. A detector can compare Math.tanh, CSS trig, compressor behavior, and timing ratios, then ask whether the claimed browser/OS profile sounds like one real machine or a Halloween costume assembled by an automation vendor with a deadline.

There is a tradeoff here, and pretending otherwise is clown makeup. Browser engines should delete old custom math code when the platform libraries are faster and maintained. A private fdlibm fork inside V8 is maintenance debt. Standard-library delegation is a sane engineering move on its own terms. The problem is that browser privacy lives in the aggregate behavior of many sane moves. One tidy cleanup can punch a new hole through a spoofing model because web-exposed determinism makes implementation choice observable.

Noise will not fix it. Randomly perturbing results produces values that match no real OS and breaks repeatability, which becomes a bigger tell. The target has to be boringly exact: if a browser presents as macOS, the exposed math needs to match real macOS Chrome down to Apple scalar library behavior, Accelerate behavior where Chrome uses Accelerate, CPU architecture edges, FMA choices, NaN handling, and timing. Scrapfly says its fix reproduces Apple libsystem_m coefficients, gates calls on claimed OS, maps Windows UCRT directly where useful, and validates 871,000 inputs against genuine hardware and real Chrome over the DevTools protocol.

The anti-bot angle makes the piece slightly grubby, because the loudest people documenting browser fingerprint seams are often selling traffic camouflage. Fine. Useful measurements come from weird incentives all the time. The mechanism does not become fake because the messenger has a product page. The web has always learned about its privacy leaks from adtech, fraud teams, scraper vendors, academic measurement labs, browser engineers, and occasional chaos goblins in HN threads. Everyone wants to identify everyone else while claiming noble reasons. Welcome to the panopticon bazaar.

The HN thread attached to the Scrapfly article is tiny, but one complaint lands: some of the source article reads like generated sales prose. That matters for taste, not evidence. The commit exists. Chrome 148 exists. The sample outputs are concrete. The routing claims are checkable against V8, Blink, and Web Audio code paths. If anything, the marketing sheen makes the technical point nastier: even a promo blog can reveal a browser boundary most users, and plenty of engineers, never considered part of privacy.

The systems-culture lesson is small and mean. Privacy boundaries rarely fail only through grand surveillance features. They also fail through tidy maintenance patches, library calls, historical C math decisions, compiler flags, DSP acceleration, and the last bit of a double. The browser is a diplomatic fiction that pretends to present one coherent identity to the web. Underneath, it is a federation of engines, libraries, OS conventions, and hardware behaviors. Fingerprinting lives in the gaps between those delegates.

Chrome 148 did not create browser fingerprinting. It made one more hidden delegate visible. That is enough. The web does not need a giant new API to leak. Sometimes it only needs std::tanh.