The Forensic Paper Trail

Every modern browser exposes a debugging protocol. Chrome exposes CDP (Chrome DevTools Protocol); every automation library you use—Playwright, Puppeteer, Selenium—talks to the browser through it. A WAF cannot see your CDP traffic directly, but the consequences of that traffic leak into the JavaScript environment in ways that are measurable and damning.

The Runtime Leaks You Must Patch

The following probes are the industry-standard leak checks that detection services like bot.sannysoft.com and creepjs.com run:

  • navigator.webdriver: The single most-shipped flag. Any value other than undefined (on Chromium) is treated as automation.
  • window.chrome: A real Chrome exposes several properties (runtime, loadTimes, csi). Headless Chromium often ships a truncated or missing object.
  • navigator.plugins & navigator.mimeTypes: Real browsers have an empty-but-present plugins array plus default PDF plugins; some sandboxes ship nothing at all.
  • Permissions API: the query() for camera/mic/notifications flips to a synthetic state in some automation modes.
  • WebGL renderer / vendor: headless profiles return "SwiftShader" software renderers instead of a real GPU string.
  • KeyboardEvent / MouseEvent coordinate tracing: Playwright-mimicked events carry a isTrusted: false flag, exposing synthetic input.
  • DevTools-induced window.outerWidth anomalies: browser window dimensions that never match a real monitor size.
  • Broken Media stacks, missing AudioContext, and createImageBitmap shape differences: cheap tells that the renderer was built without real media support.

Why Patching at JavaScript Level Is Not Enough

If you only override JavaScript properties, you are faking the symptom, not the cause. Native C++ calls that bypass JavaScript entirely—like the audio fingerprint computed from the actual backend, or the WebGL string read from the GPU driver rather than the JS wrapper—still leak the real values. That is why simple Object.defineProperty hacks fail against modern WAFs that compute fingerprints from CDP-exposed native internals.

The Connection-Level Leaks

Automation also changes transport behavior. A curl_cffi request over the network layer may look pristine; a browser automated through CDP may show:

  • HTTP/2 frames with unusual stream activity (Playwright prefetching, interrupting).
  • Extra round-trips from the DevTools protocol handshake on the same connection.
  • Websocket endpoints like /devtools/page/... in the network logs if your instrumentation is careless.
  • Timing that is fast and rigid rather than human and noisy (no cursor motion, instant clicks, instantaneous scroll).

These are harder to fix than JS flags, which is why the professional approach is to use a stealth-patched browser build or an anti-detect fork where the transport and renderer are both fixed, not just the DOM surface.

The Client-Side Test Batteries

Rather than guessing which leaks matter, reproduce the detectors' own batteries locally. Well-known scanners—bot.sannysoft.com, arh.antoinevastel.com, creepjs.com, and the open-source fingerprintjs demo—run the same probe families that commercial WAFs run. Point a hardened profile at them after every configuration change and compare the pass/fail table.

A practical rule: if a public scanner flags something, a commercial WAF almost certainly flags it too, and usually with more sensitivity. Use the scanners as a regression suite, and capture screenshots of the results so a new browser build's regressions are visible in a diff rather than discovered in a production ban wave.

Stealth Is a System, Not a Property

Patching navigator.webdriver alone is like closing one window in a house with a dozen open. A maintainable setup bundles a real browser binary, a patched automation driver, a warming routine, consistent proxy and timezone alignment, and a stable fingerprint profile. Verify with a detection scanner after every change—there is no substitute for actually measuring how many leaks remain.