Mobile userAgentData but getHighEntropyValues().model is empty — desktop faking Android (JS surface)

br.mobile_no_js_model  ·  convicts

browserlayer
coherencecategory

What it catches

The JS twin of net.ch_ua_mobile_no_model, and the blue counter to a header-only Sec-CH-UA-Model forge. navigator.userAgentData.getHighEntropyValues(['model']) is a Chromium JS surface: a REAL Android Chrome fills .model with the device hardware (e.g. 'Pixel 8'); a desktop Chromium — even under a mobile UA or Playwright/CDP device emulation (KS_DEVICE=Pixel, which sets UA + viewport + userAgentData.mobile but NOT the high-entropy model, since it reads REAL hardware) — returns an EMPTY model. So userAgentData.mobile === true with an empty getHighEntropyValues().model is a desktop faking an Android phone. CRUCIAL: this reads the JS surface, whereas net.ch_ua_mobile_no_model reads the Sec-CH-UA-Model HEADER — so a red tool that forges the header via route-interception (KS_FORGE_MODEL) evades the header check but STILL trips this one (route-interception cannot patch getHighEntropyValues). Forging the JS model too (Object.defineProperty on userAgentData) is in turn caught by br.uadata_worker_divergence (userAgentData is exposed in the Worker realm, where a main-only patch cannot reach). So the model must be coherent across HEADER + main-JS + worker-JS — three surfaces a desktop emulation cannot fill, only a real Android device. FP-SAFE: a real Android always fills the high-entropy model. GROUNDED LIVE: KS_DEVICE='Pixel 5' fires mobile_no_js_model (empty JS model); WITH KS_FORGE_MODEL (which clears the header check) it STILL fires — the header forge does not reach the JS surface.

Signals it reads

browser.mobile_no_js_model

How it fires

present

Bypassed by 10

Frontier evaders that reach the detector uncaught (scored only suspicious, defeating every convicting tell) — this check is not one that stops them. The red-team frontier this detection still has to convict.

← all detections