onionwright · how detection works
How websites detect
browser automation.
Not a list of what detection could check — the signals a real fingerprint engine actually reads, recovered by taking one apart. They fall into three layers, and where each one lives decides what can be done about it. Patching the driver reaches only the first.
1. The driver's own tells
The automation framework leaves fingerprints a page can read directly. navigator.webdriver, the CDP-injected globals, ChromeDriver's own variables.
- navigator.webdriver — checked as !== false, not === true, so deleting it is as damning as leaving it
- cdc_[22 chars]_Array / _Promise / _Symbol — ChromeDriver's injected names, matched by regex so a random suffix does not help
- __playwright__binding__, _playwrightRecorderState, and the Selenium/Phantom family — one string per framework
This is the only layer a driver-side stealth library can reach — and it reaches it by patching JavaScript after the page has already started.
2. Environment coherence
Not 'do you have a tell' but 'do your facts agree'. A spoofed platform that contradicts the real CPU architecture, a renderer string that does not match the GPU, fonts that do not match the OS.
- chrome.app / csi / loadTimes — present in real Chrome, absent in headless and several stacks. This checks what you LACK, which you cannot delete your way out of
- WebGL UNMASKED_RENDERER vs the claimed platform
- The Sec-CH-UA brand list, decided before any script runs
A stealth browser fixes some of these at the engine level. But the coherent version — a real GPU, real fonts, a residential IP — comes free from running on a real host, and cannot be synthesised convincingly on a bare server.
3. Behaviour
Whether a person is driving. This is the layer no fingerprint patch touches, and the one that is a verdict rather than a probability.
- isTrusted — false for any event synthesised in the page (el.click(), dispatchEvent). Read seven times in the engine we reverse-engineered. This is categorical: no amount of realistic timing rescues an event the browser stamped untrusted
- Mouse path — linear interpolation with zero acceleration is machine motion; humans move with jerk-limited, overshooting submovements
- Keystroke timing — 2.8 ms between keys is not a person; a person is 200–400 ms with real variance
This is what onionwright adds. The events are dispatched over CDP so they are trusted, and the timing is drawn from human motor models rather than instant jumps.
Where this leaves you
A driver-side library — patchright, puppeteer-stealth — reaches layer one and stops. A stealth browser reaches into layer two. Layer three is untouched by both, and it is the one that is a verdict, not a guess. onionwright is a patched browser plus the behaviour layer, so an agent looks like a person across all three — as far as any single tool honestly can. No tool passes everything, and one that claims to is the one to distrust.
What onionwright does about itCommon questions
What is navigator.webdriver?
A boolean the browser sets to true when it is being controlled by automation — Selenium, Playwright, Puppeteer. It is the first thing most bot checks read. Overriding it from inside the page leaves its own trace (a getter where the real browser has none), so it has to be removed in the engine to actually disappear.
Can a website detect Selenium or Playwright?
Yes, in three ways: driver artefacts (the cdc_ properties Selenium injects, CDP side effects), engine flags (navigator.webdriver, the Sec-CH-UA brand, the WebGL renderer), and behaviour (machine-timed input, events marked untrusted). Clearing one layer moves the check to the next.
How do sites detect headless Chrome?
Headless has tells beyond the flag: properties that differ from a real browser, a datacentre fingerprint, and instant, evenly-timed input. Running headful on a real host closes the environment tells for free; the behaviour still has to be driven with human motor timing to not read as a machine.