The observation problem
To understand how a site fingerprints visitors you need to see the script run from the inside: which properties it reads, which canvas operations it performs, what it sends back. The obvious move is to replace the real functions with wrappers that log and then call the original. This is monkey-patching, and it works -- until the script checks whether it is being watched. Anti-bot collectors do exactly that, so a reverse engineer ends up in an escalation ladder where each rung is harder to detect than the last.
The escalation ladder
The rungs, from easiest to least intrusive:
- Extension content script -- simplest, but it runs in an isolated world and cannot touch page functions at all.
- CDP pre-load injection -- addScriptToEvaluateOnNewDocument runs your hook before any page script, so you win the race.
- Cross-frame hooks -- sandboxed widgets run in out-of-process iframes that need per-target attachment.
- Engine-level instrumentation -- hooks inside the rendering engine that leave no JavaScript-visible trace at all.
Each JS-level rung is fragile because of one fact: a hooked function never looks exactly like a native one. The page can ask, and toString gives it away.
Why this mirrors the coherence problem
The same logic that exposes a naive fingerprint spoof exposes a naive hook. A fabricated value has to stay coherent with everything else the real machine reports, or lie detection catches the mismatch. A hooked function has to stay coherent with how a real native function presents itself -- same source string, same descriptor, same prototype -- or analysis catches that mismatch instead. In both cases the only durable answer is to work below the layer the script can inspect: at the engine level, not in JavaScript. Patching the engine itself avoids the entire leak class, which is why faithful instrumentation lives there rather than in page JavaScript.
