Skip to content

How bot detection works — and why Clearcote patches the engine

The usual approach patches navigator.webdriver, spoofs the WebGL vendor, and overrides navigator.plugins from script. Detectors still flag it — and the reason is structural, not one more property left uncovered.

Coherence, drawn: independent signals that corroborate one another spell a single identity. The two unconnected points — a spoofed value with nothing behind it — are the seam a cross-check reads.

A JavaScript spoof self-reveals

A JS spoof is a function standing where a native one belongs. A detector sets the returned value aside and interrogates whether the thing returning it is native:

The tellWhy it catches a JS spoof
toString self-revealA native method stringifies to function get vendor() { [native code] }; an override stringifies to its own source — one .toString() catches it.
Descriptor / hasOwnPropertygetOwnPropertyDescriptor exposes redefined props, and hasOwnProperty('toString') returns true on a tampered function where a native one returns false.
Wrong-this TypeErrorNative getters throw a specific TypeError on the wrong receiver; a naive shim stays quiet, and the silence is the signal.
Realm re-acquisitionA detector grabs a pristine Function.prototype.toString from a fresh iframe or Web Worker and turns it on your getter — a different realm from your main-world patch. It returns your source. Caught.

Clearcote has no such layer. The getter for navigator.userAgent is the C++ getter: it reports [native code] because it is native code, identical across every realm — main frame, iframe, and worker. There is no JavaScript hijacking to detect.

The three layers of bot detection

Modern anti-bot systems read three structurally different surfaces, in three separate places. One tool rarely fixes all three:

LayerThe tellsWhere the fix livesClearcote
A · driver / binarycdc_ ChromeDriver vars, the WebDriver protocol surfaceDrive raw CDP, skip chromedriver✅ a plain Chromium binary — no driver artifacts
B · CDP side-effectsRuntime.enable leaks, injected init-scripts, main-world execution, automation-default viewportThe control / CDP-client layer✅ the engine neutralises the Runtime.enable leak, the SDK injects no scripts by default, and launch defaults avoid the automation-default viewport (where your client runs its own scripts is up to it)
C · fingerprint surfacecanvas, WebGL, audio, fonts, navigator, TLS — across main frame, iframes, workersThe engine (C++), because JS overrides self-reveal✅ this is Clearcote

The thing that matters most: no spoof-vs-real seam

Because the controls live in the engine, the JavaScript a page sees and the network handshake underneath it come from one real Chromium. There is no spoofed-JS-over-real-TLS seam for a cross-check to catch — the exact failure mode that gives injection-based tools away. One --fingerprint seed produces a single, internally consistent machine across canvas, WebGL, audio, fonts and hardware. The TLS and HTTP/2 handshake underneath is the engine's own, and it agrees because the persona reports the version the engine really is — leave brandVersion unset to keep it that way.

And when the noise itself is the tell, switch it off — canvas and WebGL return their natural values while the identity spoof stays on (rendered audio and client rects are never perturbed either way, and the AudioContext rate/latency scalars keep following the persona; here is why):

javascript
await launch({ fingerprint: "u1", fingerprintNoise: false });   // identity on, per-site farbling off

With the noise off, identities on the same machine share one canvas hash. canvasNoise: false turns off canvas 2D only and keeps the WebGL noise (licensed build, 150 r12+).

Network and locale coherence

The persona is only half of it: the timezone, language and WebRTC address also have to agree with where the traffic comes from. Behind a proxy, launch with geoip — the SDK looks up the proxy's exit IP and sets the timezone, language list, geolocation and WebRTC address for that region, and stops the launch if it can't resolve one. Without it, the timezone follows the language (en-US means New York), whatever country the proxy is in. See Playwright & Puppeteer for the options.

This is why Clearcote patches the C++ engine instead of injecting JavaScript. See Fingerprint flags for what you control, and Architecture for how the engine keeps every signal coherent together.