Trusted Types hands back the branded object its own type demands
Check id trusted-types-value-invariant
What an ordinary browser yields
createScript returns a branded object (typeof is not "string") that stringifies back to its input; isScript reports true for that object and false for a plain string; and the object is an instance of the native TrustedScript where one exists.
What a detector infers
Trusted Types is a typed API, not a boolean feature flag, and that is the whole of the check. When you build a policy and call createScript(s), the browser is required to hand back a branded TrustedScript object — never the raw string — that stringifies losslessly back to s; isScript is required to be a type-guard that returns true for exactly those objects and false for a plain string; and where the browser exposes a native TrustedScript constructor, the object must be an instance of it. These are self-referential spec invariants: they hold on genuine Chromium, on Firefox's shipping implementation, and on the official W3C polyfill, and no reference corpus is involved. The reason they are worth asserting is that Trusted Types is a tempting thing to fake. reCAPTCHA's BotGuard loader depends on it directly — it creates a policy named "bg" and its bootstrap gates on eval(policy.createScript("1")) returning 1, so a browser that cannot produce a real, eval-able TrustedScript never gets past the loader. A stub trustedTypes installed to satisfy a naive presence probe, or even to satisfy this page's own function-toString integrity checks on createPolicy and createScript, gives itself away the instant its createScript OUTPUT is inspected: a stub returns the string it was handed, and the string is not a TrustedScript. That is a step deeper than any toString probe on the page, which only ever asks whether a function looks native — this asks whether the object that function returns obeys the function's own type. The check never evaluates the script, because eval interacts with a page's Content-Security-Policy; the lossless round-trip is the CSP-safe proxy for eval-ability. It also declines rather than fails on every honest edge: absent on Safari and older Firefox, and a CSP that refuses an unknown policy name is page configuration, not a browser tell.
How to resolve it
Do not stub window.trustedTypes to fake the API's presence. It is a typed interface: createScript must return a real TrustedScript that round-trips to its input and that isScript recognises, not the raw string. A stub satisfies a presence check and then contradicts its own type contract the moment its output is examined. If the API must exist, use the one the browser actually ships, or the official W3C polyfill — both honour these invariants.
Read in the wild by
reCAPTCHA / BotGuard
Its loader creates a Trusted Types policy named "bg" and its bootstrap will not proceed unless eval(policy.createScript("1")) returns 1 — so it structurally depends on createScript producing a genuine, eval-able TrustedScript rather than a stub that echoes the string.
assets/bg.js: G.createPolicy("bg", {createHTML: u, createScript: u, createScriptURL: u}), gated on (G = V()) && b.eval(G.createScript("1")) === 1 (present twice)
elyelysiox/recaptcha-botguard — the BotGuard loader + VM disassemblerEvery attribution traces to a published artifact. See the sources and their limits.
Nearby checks in Navigator identity
- 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… - a worker resolves the same IANA timezone as the main thread
worker-main-timezone-coherenceA browser has one host timezone, and the specification ties both the main-thread global and every worker global to it, so a real browser… - the keyboard layout maps the OEM keys the way one real Windows layout would
keyboard-layout-single-originA keyboard layout is not a set of independent key mappings — it is one artifact the operating system hands over whole. - navigator.appVersion is the user agent minus its leading token
appversion-vs-useragentnavigator.appVersion is not an independent fact. In Blink and WebKit it is computed from the User-Agent at read time — literally the UA with… - the user agent on the wire and the one JavaScript reports describe the same browser
ua-wire-vs-navigatorYour user agent is asserted on two channels built by different layers: the network stack writes the header, and the renderer answers… - a Date's own text agrees with the Date's own offset
date-tostring-coherenceA Date's text is not opaque. ECMA-262 defines toString() as toDateString() + " " + toTimeString(), fixes the weekday and month tables to… - Origin-scoped storage and browsing state
origin-storage-historyCookies, storage, IndexedDB, and same-tab history reveal only this origin's state. - Profile age and cross-site history boundary
profile-age-boundaryPrivacy boundaries prevent reading global history or true profile age.
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 trusted-types-value-invariant belongs to.