JA4 browser family contradicts User-Agent browser
net.tls_vs_ua_browser · convicts
What it catches
The core cross-layer thesis tell: the TLS engine (JA4) below the JS spoofing layer must agree with the browser the UA claims. A Chromium-TLS client wearing a Firefox/Safari UA disagrees. v0.74.15 FIRST LIVE POSITIVES + a robustness fix: previously 'evaded' because the edge JA4→browser hint table keyed on a FULL JA4 (incl. JA4_c, the extension/sig-alg hash) — and the real headful Chromium's JA4_c (d8a2da3f94cd) differs from the seeded one (02713d6af862), so REAL Chrome itself got ja4_browser_hint=None (unhinted → rule unevaluable). Fixed by adding the Chromium cipher-suite PREFIX (t13d1516h2_8daaf6152771 → chrome) to edge/internal/fingerprint/ja4_hints.json; the Lookup already falls back to the JA4_a+JA4_b prefix (cipher-level identity is the stable per-engine key, robust to JA4_c randomization — itself an anti-detect technique Camoufox uses). Now: real headful Chromium → prefix=chrome, ua_browser=chrome → MATCH, no fire (GROUNDED, FP-safe); spoof-ua (chrome TLS + Firefox UA) and ios-ua-spoof (chrome TLS + Safari UA) → prefix=chrome, ua_browser=firefox/safari → fire (two live positives). Chrome-impersonators with no JS (primp/curl-impersonate, ua_browser absent) don't fire — the not_equal predicate needs both reads. Browserforge calibration has no JA4, so promotion cannot raise the legit flag rate. v0.74.19: extended the hint table to the WebKit engine — the real WebKit TLS prefix (t13d2914h2_723694b0fccc -> safari) was unhinted, so a WebKit-engine bot faking a non-Safari UA evaded this rule. GROUNDED + FP-SAFE: a real WebKit + Safari UA (the headful capture) now hints safari == ua_browser safari -> MATCH, no fire; the new webkit-ua-spoof evader (Playwright WebKit under a Chrome UA) -> safari != chrome -> fires (live positive). FP-safe even though Playwright WebKit's TLS may differ from Apple Secure Transport's real-Safari JA4: no real USER produces this JA4 (it is the WebKit-engine handshake), so hinting it safari can only ever match a WebKit+Safari session (no fire) or catch a WebKit-engine UA-spoof. Caveat: real macOS/iOS Safari has its own (unverified-in-sandbox) JA4 and stays unhinted until a real-Safari capture is available — a coverage gap, not an FP. The hint table now identifies all three engines (chromium/firefox/webkit). v0.74.30 FP FIX (GROUNDED on real Microsoft Edge 149): predicate not_equal -> not_equal_browser. Edge/Brave/Opera/Vivaldi ship the SAME Chromium TLS engine, so a real Edge's JA4 hints 'chrome' while ua_browser is 'edge' — a literal not_equal convicted every real Edge (the 2nd-most-common desktop browser) on net.tls_vs_ua_browser. not_equal_browser collapses the Chromium family to its engine before comparing (edge/brave/opera/vivaldi/samsung -> chrome), so a Chromium-engine UA matches a 'chrome' hint (no fire) while a CROSS-ENGINE pairing still fires. Re-validated LIVE: real Edge 149 no longer fires; spoof-ua (chrome TLS + Firefox UA), ios-ua-spoof (chrome TLS + Safari UA) and webkit-ua-spoof (WebKit TLS + Chrome UA) still fire — no detection loss.
Signals it reads
network.ja4_browser_hint browser.ua_browser
How it fires
not_equal_browser
Evaders it caught 4
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.