Skip to content
All fingerprint checksError subsystem

the structured and string renderings of one call chain name the same frames

Check id structured-stack-vs-string-stack

What an ordinary browser yields

Every frame of our own probe chain that the structured rendering names also appears in the string rendering.

What a detector infers

Chrome describes one call chain twice. Once as the human-readable string on Error.stack, and once as structured CallSite objects, through a hook called Error.prepareStackTrace. DataDome only ever reads the string — its VM hashes the text, and the field it ships names the originating script URL — so the structured rendering is a second witness to the same event that the detector itself never consults. If the string omits a function the structured view says was definitely on the stack, the string was edited after the engine wrote it; scrubbing an injected script's frame out of a hash is the motivated behaviour here. Two lessons here were found by running the check rather than reasoning about it, and the first one shipped as a bug. V8 formats a stack LAZILY, on first access to .stack, through whatever prepareStackTrace is installed at that moment — so reading the string rendering while our own array-returning hook was still active handed back the CallSite array instead of a string, and testing `array.includes(name)` looks for an element equal to that name rather than a substring. Every frame therefore read as missing, and the check accused every stock V8 browser of editing its stack. The fix is an ordering: construct the Error inside the call chain, read the CallSites under the hook, restore the hook, and only then read .stack — which yields both renderings of the same chain. The second lesson: in V8, `stack` is an OWN ACCESSOR on each Error instance, and Error.prototype has no stack property at all. That means a spoofer's per-instance getter intercepts BEFORE prepareStackTrace ever runs — so the obvious implementation, throwing an Error and reading its .stack, hands the spoofed string straight back and goes quiet against the exact tool it was built to catch. The independent reading has to come from Error.captureStackTrace applied to a plain object we own, which a hooked Error constructor cannot reach. The check speaks only when V8's own documented API is present, native and functioning: if captureStackTrace is missing, or is an npm shim rather than the real thing, or if something legitimate already owns prepareStackTrace — Sentry and source-map-support both do — it reports nothing rather than guessing.

How to resolve it

Both renderings are generated by the engine from the same call chain, so editing one in isolation is visible from the other. If the goal is to keep an injected script's URL out of a stack hash, note that the structured view still names it — and that the tool doing the scrubbing is what created the disagreement in the first place.

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

Read in the wild by

DataDome

Consumes only the STRING rendering of the stack — its VM hashes the text, and the field it ships names the originating script URL. The structured CallSite rendering of the same call chain is a second witness it never consults, which is exactly what makes it useful here.

out.txt 0x4927-0x4931 (NEW_U8 0, PUSH_IMM "stack", GET, feeding the hash loop); payload field aTJPJW carries a frame naming geo.captcha-delivery.com

xKiian/datadome-vm — DataDome bytecode VM disassembly

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 structured-stack-vs-string-stack belongs to.