Chromium 148 Made Math.tanh an OS Fingerprint
A quiet V8 switch to host std::tanh now leaks Linux, macOS, or Windows from a single double.
Fingerprint detectors already scrape canvas, WebGL, fonts, and audio. Since Chromium 148 they also have a quieter signal: the last bits of Math.tanh. One carefully chosen input returns different doubles on Linux, macOS, and Windows Chrome. That is not noise. It is a host libm signature that can contradict a spoofed User-Agent in a single comparison.
The change is recent and narrow. Until Chrome 148, V8 computed tanh with a bundled fdlibm port, so every OS returned identical bits. A commit replaced that path with std::tanh, which calls the platform math library. The first release that shipped the switch was V8 14.8.57, corresponding to Chrome 148. Chrome 147 and earlier do not leak this way. 148 and later do.
This is a genuine shift for anyone who has to keep a browser consistent with a claimed platform (anti-bot clients, automation fleets, privacy browsers). It is not hype, and it is not theoretical. It is a 1 ULP split that anti-bot systems can table-lookup.
Why the last bits disagree
IEEE 754 defines how a double is stored. It does not require sin, cos, tanh, or exp to be correctly rounded. Correct rounding is expensive, so each OS ships a libm with its own minimax coefficients, tables, and reduction constants. Three libraries matter for Chrome:
- Linux: glibc
- macOS: Apple
libsystem_m - Windows: UCRT (
ucrtbase.dll)
They agree on most inputs and diverge often enough to classify the host. Measured on real Chrome 150 machines over the DevTools protocol:
| Call | Linux (glibc) | macOS (libsystem_m) | Windows (UCRT) |
|---|---|---|---|
Math.tanh(0.5) |
0.46211715726000974 | 0.46211715726000974 | 0.46211715726000974 |
Math.tanh(0.7) |
0.6043677771171636 | 0.6043677771171635 | 0.6043677771171635 |
Math.tanh(0.8) |
0.6640367702678491 | 0.664036770267849 | 0.6640367702678489 |
Math.tanh(0.9) |
0.7162978701990245 | 0.7162978701990245 | 0.7162978701990244 |
tanh(0.5) is a useless probe. Everyone agrees. tanh(0.8) separates all three at once, with a two-ULP spread. Roughly a quarter of inputs show a Linux-vs-macOS split; Windows disagrees with both on a few percent. A detector needs no deep math. It needs a short table of genuine patterns and a comparison against the claimed OS.
The asymmetry that makes it checkable
If every Math.* call leaked the host, spoofing would be hard but uniform. The real problem is narrower: only Math.tanh currently routes to host libm among the JavaScript Math functions. The rest stay identical across OSes because V8 ships them statically (llvm-libc for most, a glibc-derived dbl-64 path for sin/cos). Spoof tanh to look like macOS while leaving the bundled functions alone and you still look fine on those. Spoof the bundled ones too and you create a new inconsistency.
That one-function leak is itself a signal. A client that claims macOS, returns Linux tanh bits, and still matches the known V8 constants for exp/pow/atan has contradicted itself twice over.
The surface is larger once you leave pure Math:
- CSS
sin(),cos(),atan2()and the rest do not share code withMath.sin. The layout engine reduces angles in degrees, then calls platformstd::sin(and friends) on the reduced value. All seven CSS trig functions hit hostlibm. - Web Audio on Mac routes vector work and FFTs through Accelerate (
vvsin,vvtanh, vDSP). Scalar paths (compressor per-sample transcendentals) still uselibsystem_m. The two Apple libraries disagree on 10–89% of inputs depending on the function.cos(0)is exactly1.0in scalar and0.9999999999999999under Accelerate. - Architecture also drifts: ARM vs x86 fused-multiply-add and NaN sign propagation can move results even when the source algorithm matches on paper.
In short, host libm is bold on the routing map for Math.tanh, CSS trig, and parts of Web Audio. Everything V8 still bundles is safe to leave alone. Get the library wrong for a call site and you land 1 ULP off on most inputs, which is worse than not spoofing.
What this means for browser automation and anti-fraud
If you run headless or instrumented Chrome and assert a platform (via User-Agent, Client Hints, or navigator.userAgentData), you now need the math bits to match that claim. A single probe is enough:
// Quick OS-class probe (Chrome 148+)
const t = Math.tanh(0.8);
// Linux glibc: 0.6640367702678491
// macOS: 0.664036770267849
// Windows UCRT: 0.6640367702678489
console.log(t.toPrecision(17));
Practical consequences split by role.
Anti-bot / scraping fleets. Consistency checkers already cross User-Agent against canvas, WebGL vendor strings, and font sets. Adding Math.tanh (and a handful of CSS trig probes) is cheap on the detector side and expensive on the emulator side. Claiming macOS while returning glibc bits is an immediate fail. The same applies to CSS calc(sin(...)) style expressions, which take a different code path and still hit host libm.
Fingerprint browsers and privacy tools. Projects that rewrite platform signals (for example fingerprint-chromium style forks that expose --fingerprint-platform) must either (a) keep Math.tanh on the real host and accept that the OS claim is only partial, or (b) reimplement the target libm to bit-identical results for the call sites Chrome actually uses. Option (b) is the hard one: only some math leaks, JS and CSS paths differ, macOS has two disagreeing libraries, and ARM/x86 FMA behavior can undo a paper-correct port.
App and site developers. If you build fraud, abuse, or session-risk systems in the browser, this is a low-cost secondary signal. It does not replace canvas or WebGL, but it is stable, hard to randomize honestly, and fails closed when a client lies about its OS. Prefer a small table of known good values for Chrome 148+ over any attempt to "score" floating-point quality. Avoid treating older Chrome the same way: pre-148 builds will not show the split.
What not to do. Do not blanket-reimplement every Math function for the spoofed OS. Most of them still come from V8's static libs and must stay OS-invariant. Do not assume Apple math is one library. Do not copy scalar libsystem_m results into Web Audio paths that actually call Accelerate. And do not treat Tor-style "disable high-precision Math" patches as a free lunch either: the class of issue (OS-specific math precision as a fingerprint) has been on the Tor bug tracker for years, and blunt removal is itself a detectable rarity.
Production readiness and the real trade-offs
For detectors, the signal is production-ready today on Chrome 148–150. One call, one table lookup, clear separation on known inputs. False positives mainly come from non-Chrome engines or from builds that still ship the old bundled tanh (Electron and other V8 embeds may lag or pin differently; measure them, do not assume).
For spoofers, it is not production-ready to "just match macOS." Matching requires:
- Knowing which Chrome version you emulate (148+ only for this leak).
- Knowing which library backs each call site (scalar vs Accelerate on Mac).
- Matching architecture-level FMA and NaN behavior if you cross-compile.
- Leaving V8-bundled Math alone so you do not invent a second fingerprint.
Until those are solid, the safer operational choice is to claim the OS you actually run, or to run real Chrome on the claimed OS under a remote debugging protocol and read the doubles from the genuine binary. That is how serious anti-detect stacks already close hard signals: drive the real browser, do not approximate it.
Math fingerprinting is old news in privacy engineering. What changed in 148 is that a previously neutralized path (tanh via fdlibm) was reopened to the host, and only that path among JS Math. The result is a small, reliable OS classifier that fits the modern anti-bot habit of cross-checking every surface against every other. If your stack asserts a platform, treat Math.tanh(0.8) as part of the contract now, not as trivia from the floating-point annex.
Sources & further reading
- Since Chronium 148, Math.tanh is now fingerprintable to link underlying OS — scrapfly.dev
- GitHub - adryfish/fingerprint-chromium: An open source fingerprint browser based on Ungoogled Chromium. 指纹浏览器 隐私浏览器 · GitHub — github.com
- CrOS EC (Embedded Controller) - Fingerprint Firmware (FPMCU) — chromium.googlesource.com
Ji-ho covers the increasingly tangled overlap between cloud architecture and security, drawing on a background as a penetration tester to keep his reporting grounded in real-world attack paths. He never lets a vendor claim go unquestioned and insists that every buzzword come with a proof of concept.
Discussion 0
No comments yet
Be the first to weigh in.