the Keyboard API's presence matches the claimed engine and form factor
Check id keyboard-api-vs-platform
What an ordinary browser yields
navigator.keyboard is present on desktop Chromium and absent on mobile Chromium — matching the form factor the user agent claims.
What a detector infers
The Keyboard API — navigator.keyboard, with getLayoutMap and lock — ships in desktop Chromium and nowhere else: not in Gecko, not in WebKit, and not in Chromium's own mobile builds. Its presence is therefore a statement about the engine and the form factor simultaneously, made by the binary rather than by the user agent string. The check compares that statement against what the UA claims: a mobile Chrome UA on a build that has the API is a desktop browser wearing a phone's name. Two guards keep it honest, and both exist because real browsers disagreed with the first draft. It tests PRESENCE rather than truthiness, because Brave neutralises the API as a fingerprinting defence by making the getter return null while leaving the property on Navigator.prototype — the truthiness form failed every stock Brave desktop install. And it declines entirely when a desktop UA appears on touch-primary hardware, because Chrome for Android's Request-desktop-site — a one-tap setting, and the default on large-screen tablets — rewrites navigator.userAgent to the Linux desktop string itself. A string the browser vendor rewrites on the user's behalf is not a form-factor claim worth adjudicating. The direction that matters, a desktop build behind a mobile persona, is still fully asserted, and that one the UA cannot fake away. Note carefully what is NOT asserted. getLayoutMap() resolves the OS's physical keyboard layout, which would corroborate the claimed platform beautifully — QWERTY versus AZERTY versus QWERTZ — and it was tempting for exactly that reason. It was dropped after measurement: on a real Chrome it returned an EMPTY map, because the API needs focus and yields nothing in an unfocused or embedded context. A check built on it would have failed honest browsers sitting in a background tab. Presence is the part that holds; the map is the part that would have been a false positive.
How to resolve it
The API's presence follows the build, not the UA. Presenting a desktop identity from a mobile build (or the reverse) leaves the surface disagreeing with the string — use a build whose form factor matches the identity you present, rather than adding or deleting the property to paper over the gap.
Read in the wild by
Kasada
Probes the Keyboard API, a Chromium-desktop-only surface.
keyboard_layout_map
Kasada ips.js teardown — the full 427-probe listEvery attribution traces to a published artifact. See the sources and their limits.
Nearby checks in Navigator identity
- 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"… - navigator.deviceMemory is a power-of-two spec value
device-memory-boundsnavigator.deviceMemory does not report actual RAM — the spec requires the UA to quantise it to a coarse power-of-two rung before exposing… - navigator.hardwareConcurrency is plausible
hardware-concurrencyThis is an info-severity observation, not a scored failure — it reports navigator.hardwareConcurrency and only notes whether the value is an… - the machine delivers no more parallelism than it claims cores
hardware-concurrency-vs-parallelismThe plain hardwareConcurrency row can only ask whether the number looks sane, because no second API reports a core count. - browser-brand and context hints agree with a fresh realm
brand-hints-vs-realmSome of the cheapest signals to fake are the ones that say which browser this is: navigator.brave exists only in Brave, window.duckduckgo… - location.origin is the serialisation of its own protocol, host and port
location-origin-coherenceAn origin is not stored next to a URL, it is serialised from it: for an http or https document, origin is exactly the scheme, followed by… - navigator.vendor is the constant the measured engine compiles in
navigator-vendor-vs-enginenavigator.vendor is not a preference or a setting — it is a string constant compiled into the engine. - navigator.oscpu names the same OS family as the user-agent
ua-oscpu-os-family-coherencenavigator.oscpu is a property only Gecko still exposes — Chromium removed it and WebKit never had it — so this check is silent on every…
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 keyboard-api-vs-platform belongs to.