navigator.platform ↔ UA ↔ UA-CH platform agree
Check id platform-coherence
What an ordinary browser yields
All three surfaces resolve to the same OS family — e.g. a Windows UA alongside platform "Win32" and UA-CH platform "Windows".
What a detector infers
Three independent surfaces each name an operating system, and this check normalises all three to one family (win / ios / mac / lin) and requires them to match. It classifies the UA string by regex, navigator.platform by its own prefix (Win/Mac/Linux/arm), and navigator.userAgentData.platform by its label; when UA-CH is absent it is treated as agreeing rather than penalised. The mechanism a detector relies on is that these fields are populated from one source in a real build, so they cannot disagree by accident — a mismatch means at least one was written by hand. This is severity critical because the inference is essentially free and essentially certain: no configuration error inside a real browser produces a Windows UA with a MacIntel platform.
How to resolve it
Drive all three from one persona. Keep --fingerprint-platform coherent with the UA string and the UA-CH platform value rather than overriding any one of them individually.
Read in the wild by
Kasada
navigator.platform, the user-agent string and appVersion are each collected as their own slot, in both the page and the iframe realm.
probes.txt: window_navigator_platform, window_navigator_user_agent, window_navigator_app_version, iframe_navigator_platform, iframe_navigator_user_agent, iframe_navigator_app_version (blog.txt batch 0 slot 124: window_navigator_app_version(fn_129709))
PerimeterX / HUMAN
The deployed PerimeterX collector reads all three OS-naming surfaces in the same script: navigator.platform, navigator.userAgent, and the UA-CH platform hint from getHighEntropyValues. The study's own field table annotates the platform field as one that must match the UA.
Deployed PerimeterX collector main.min.js (client.px-cloud.net/PXO1GDTa7Q/main.min.js, SHA-256 e042d5de83…a754e), with hU=navigator: `t[0][hQ(844)]=hU.platform` where hQ_map.json 844="dEABCjEhBjA=" (observed "Win32", src EV1+EV2); `t[0]["RBBxWgJydmw="]=hU[hQ(845)]` where hQ(845)="userAgent" (observed "Mozilla/5.0 (Windows NT 10.0; Win64; x6…"); `t[0]["R3cyPQIXMQ4="]=pj[hQ(875)]` where hQ(875)="platform" and pj is the navigator.userAgentData.getHighEntropyValues result (observed "Windows"; adjacent pj.brands / pj.mobile / pj.uaFullVersion confirm pj). Study annotation — EV1_EV2_UNIFIED_REFERENCE.md:50 row #2: "| 2 | **navigator.platform** | string | `dEABCjEhBjA=` | `KDRePm1VWgQ=` | `navigator.platform` (e.g. `\"Win32\"`, `\"MacIntel\"`); must match UA | STATIC |".
CreepJS
CreepJS, an open-source fingerprinting research demo rather than a production detector, classifies navigator.platform and the User-Agent string into OS families and records a documented lie when the two disagree; it compares those two surfaces only — UA-CH platform is not part of that comparison.
src/navigator/index.ts L42-49: `// user agent os lie` / `if (USER_AGENT_OS !== PLATFORM_OS) { lied = true; documentLie(\`Navigator.platform\`, \`${PLATFORM_OS} platform and ${USER_AGENT_OS} user agent do not match\`) }` — both values come from src/utils/helpers.ts L188, `const [USER_AGENT_OS, PLATFORM_OS] = getReportedPlatform(navUserAgent, navPlatform)`, which regexes navigator.userAgent and navigator.platform into WINDOWS/LINUX/APPLE/OTHER.
Every attribution traces to a published artifact. See the sources and their limits.
Nearby checks in Navigator identity
- UA-CH brand version matches the UA Chrome version
uach-ua-versionThe check extracts the major from Chrome/(\d+) in the UA string, finds the Chrome or Chromium entry in navigator.userAgentData.brands, and… - UA-CH high-entropy hints cohere with the UA
uach-highentropyThis awaits navigator.userAgentData.getHighEntropyValues and compares the richer hints against the UA string on three axes, collecting every… - every brand in a UA-CH list uses the same version format
uach-brand-version-arityUA-CH publishes the same brand set twice at two precisions. navigator.userAgentData.brands carries bare majors like "148", and the… - navigator.language === languages[0]
languages-coherenceIn a real browser navigator.language is defined as the first element of navigator.languages — one preference list, two views onto it. - the host voice roster represents the language the browser claims
locale-vs-voice-coherenceThe voice roster is installed at the operating-system level and the language preference is set in the browser, so the two are genuinely… - eval.toString().length matches the engine (V8 = 33)
etslThe string a JS engine returns for a native function's source is an implementation detail, and its length differs per engine… - navigator.productSub === 20030107 (Chromium)
product-subnavigator.productSub is a frozen legacy constant, not a live value: Chrome, Safari and Opera return "20030107" while Firefox returns… - a Firefox user-agent reports the Gecko productSub constant 20100101
gecko-productsub-hardcodenavigator.productSub is a constant compiled into the engine, and the two families disagree: every Gecko build hardcodes it to "20100101"…
Clearcote is a browser built for fingerprint coherence
It is a Chromium fork, maintained by the same people who wrote this reference. It ships as a compiled browser rather than as a stealth script injected into someone else's — which is a description of how it is built, and is not an argument about how it behaves on this check.
This audit takes no position on how Clearcote scores on Navigator identity checks, on this one, or anywhere else. It has no baseline corpus of other people's fingerprints to rank you against and no vendor scoreboard — nearly every check is self-referential, asking one browser the same question through two independent APIs and reporting whether both answers can be true at once. It runs identically on any browser, including ours. Run it on yours and read the result yourself.
See the other checks in Navigator identity — the family platform-coherence belongs to.