Scrapeless Wiki

What Is JavaScript Hook Detection? – Definition and How It Works

Informational P2 what is javascript hook detection

Learn how sites detect patched JavaScript functions via toString, prototype checks, and property descriptors.

JavaScript hook detection finds evidence that the page's JavaScript environment has been modified β€” functions replaced, properties redefined, APIs wrapped. It is how anti-bot scripts catch stealth patching.

1. Why Patching Leaves Traces

Automation frameworks and stealth plugins hide themselves by overriding APIs β€” replacing navigator.webdriver, faking navigator.plugins, patching WebGL parameter lookups.

Every override modifies the environment, and JavaScript is introspective enough that modifications are observable. The detection question is not "what does this property say" but "has this property been tampered with".

2. The toString Check

The best-known technique. A native function stringifies distinctively:

Function.prototype.toString.call(navigator.permissions.query)
// native:  "function query() { [native code] }"
// patched: the actual replacement source

A function that should be native but stringifies as JavaScript source has been replaced. Stealth libraries counter by patching Function.prototype.toString itself β€” which is then detectable by the same check applied to toString, producing an infinite regress that the defender generally wins, because they only need to find one inconsistency.

3. Property Descriptor Inspection

Redefining a property changes its descriptor, and descriptors are readable:

Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver')
// genuine: {get: f, set: undefined, enumerable: true, configurable: true}

Checks that catch patching:

  • A property defined as a value where the genuine one is a getter.
  • A property present on the instance rather than the prototype.
  • configurable or enumerable flags that differ from the real implementation.
  • A getter whose toString is not native.

The instance-versus-prototype check is particularly effective: navigator.webdriver genuinely lives on Navigator.prototype, so an own property on the navigator instance is proof of override.

4. Prototype Chain Integrity

Object.getPrototypeOf(navigator) === Navigator.prototype
navigator.constructor.name === 'Navigator'
Navigator.prototype.hasOwnProperty('webdriver')

Wrapping objects in proxies, or reassigning prototypes, breaks these identities. A Proxy is itself detectable in several ways β€” timing differences, behaviour on unusual operations, and inconsistencies when the same object is reached by two paths.

5. Error Stack Inspection

Throwing and inspecting a stack reveals injected frames:

try { null.x; } catch (e) { /* e.stack may name injected scripts */ }

Injected content scripts, extension URLs, and wrapper functions can appear in stack traces, which is why some detection deliberately triggers errors inside API calls to see what the call path looks like.

6. Timing Differences

A wrapped function is slower than a native one, because the wrapper executes too. Measuring thousands of calls to a hot API can distinguish native from patched even when every structural check has been defeated.

This is the hardest class to counter, because it measures behaviour rather than declarations β€” the same principle that makes performance probes effective against spoofed hardware.

7. The Strategic Implication

Hook detection is an asymmetric contest. The defender needs one inconsistency; the patcher needs every check covered, including checks they have not seen.

The practical conclusion for collection work: prefer an environment that needs no patching over one that is heavily patched. A genuine browser, in a genuine configuration, with a real profile and a matching network location, presents no hooks to find. An elaborately patched headless browser presents a large surface, and each patch is a potential tell.

Where patching is unavoidable, keep it minimal and coherent rather than comprehensive and contradictory.

8. Summary

JavaScript hook detection looks for evidence of a modified environment: functions whose toString is not native, properties redefined as values instead of getters or moved from prototype to instance, broken prototype identities, injected stack frames, and timing overhead from wrappers.

The contest is asymmetric β€” one inconsistency is enough for the defender β€” so the durable approach is an environment that requires no patching at all, rather than a patched one that must survive every check including the unpublished ones.