Skip to content
All fingerprint checksError subsystem

every realm speaks the same error dialect

Check id error-dialect-realm-agreement

What an ordinary browser yields

Every reachable realm returns a byte-identical signature — the same eight messages, the same stack dialect, the same location for the stack property.

What a detector infers

An engine's error messages are a dialect hiding in plain sight. Ask V8 to read a property of null and it says "Cannot read properties of null (reading 'x')"; SpiderMonkey says "x is null"; JavaScriptCore says "null is not an object (evaluating 'x')". None of that is specified — it is wording compiled into the binary. The check evaluates one shared source string in the page, in a fresh iframe realm and in a worker, provoking eight deliberate throws across eight error classes (reading a property of null, of undefined, calling a non-function, reducing an empty array, toFixed(999), Array(-1), JSON.parse('{'), decodeURIComponent('%')), and adds two structural facts: the stack's shape classified only as AT-style or at-sign style, and where the `stack` property lives (an own accessor, an own data property, or on the prototype). One engine serves all three realms, so all three must answer identically. DataDome is provably harvesting exactly this: one of its fields base64-decodes to "WorkerCaughtErr: Err: TypeError: Cannot read properties of null (reading 'getExtension')" — it builds an OffscreenCanvas inside a Worker, gets a null context back, calls getExtension on it, and ships the resulting message. The failure is the payload; it is not recovering from that error, it is collecting it, from the realm most tooling forgets. Two design choices are worth stating because they are what keep the check honest. It never names an engine, a version or a phrase, so there is no table to go stale — an engine nobody has ever seen simply agrees with itself and passes. And all three realms must evaluate the identical source bytes, because every engine quotes your own source text back inside these messages: if the page evaluated an inlined copy while the worker evaluated a string, our own minifier would rename the variables in one and not the other, and Firefox and Safari would then genuinely report different text for the same operation, failing a completely stock browser.

How to resolve it

Error messages are produced by the engine, and the engine is shared by the page, its iframes and its workers. Anything rewriting them in one realm cannot reach the others, because a fresh realm is constructed straight from the binary. If a stack or a message must be hidden, note that it has to be hidden identically in every realm at once — which is considerably harder than leaving it alone.

Why does Function.toString() reveal a hooked function?

Read in the wild by

DataDome

Provokes a null-context TypeError INSIDE a Worker — it builds an OffscreenCanvas, gets a null WebGL context back, calls getExtension on it, and ships String(e) verbatim. The failure is the payload: it is not recovering from that error, it is harvesting the engine's exact message phrasing from the realm most tooling forgets.

payload field mUMHqX, base64 -> "WorkerCaughtErr: Err: TypeError: Cannot read properties of null (reading 'getExtension')"; main-thread counterpart aTJPJW = "Error\nat na (https://geo.captcha-delivery.com/captcha/?..."

glizzykingdreko/datadome-encryption — a real 232-field captured payload

Akamai

Lists error-message format as an explicit signal in its JavaScript-environment category, on the stated grounds that the wording differs by engine.

analysis/signal_categories.md, section 6 "JavaScript Environment": "Error messages — Format of error messages (differs by engine)"

Edioff/akamai-analysis — Bot Manager v2 signal inventory

Every attribution traces to a published artifact. See the sources and their limits.

Other checks in Error subsystem

Who builds this test

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 Error subsystem 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 Error subsystem — the family error-dialect-realm-agreement belongs to.