CDP detection is how anti-bot systems catch browsers driven by the Chrome DevTools Protocol — the control channel Puppeteer, Playwright, and Selenium's Chromium driver all use internally — by probing side effects of the protocol itself, most famously a JavaScript getter that only fires when the Runtime.enable domain has been switched on. It catches automation even when every static fingerprint looks perfect.
CDP works by having a controller send domain-enable commands — Runtime.enable, Console.enable, and others — over a WebSocket, after which the browser starts pushing matching protocol events back. To relay a console.log() call's arguments across that channel, Chrome has to serialize them, and serializing a JavaScript object invokes any getter defined on its properties. A page can plant a getter on an object's id (or toString, or a numeric coercion) and log that object; if Runtime.enable is active, the getter fires and reveals the automation channel, even though nothing in the page's own script ever read that property directly. A normal, non-automated Chrome tab never has Runtime.enable switched on, so this probe reads as high-confidence automation evidence.
Console.enable produces a similar tell, and it isn't the only leak in this family: stale bindings left behind by automation frameworks (globals like window.__playwright__binding__, or ChromeDriver's older cdc_-prefixed properties) and CDP-dispatched input events — which lack a real browser's isTrusted flag and natural timing jitter on clicks and keystrokes — round out the same class of control-channel detection.
Patching navigator.webdriver, spoofing canvas output, and matching a real browser's TLS handshake all fix static, one-time signals — none of that touches the CDP channel itself, which only exists because Puppeteer, Playwright, and Selenium's Chromium mode connect over CDP to drive the browser in the first place. A scraper can look flawless on every static check covered elsewhere in this glossary and still get caught the moment a page runs one of these probes, because the leak comes from the control mechanism, not from anything the script author wrote into the page.
Closing it requires the automation framework itself to be patched so it never enables the leaky domains, or suppresses their observable side effects — forks like Patchright (a patched Playwright) and the rebrowser-patches project exist specifically for this, rather than something fixable from outside the framework.
How Crawlora handles this
Crawlora's browser-rendering layer runs on patched, continuously-updated engines that avoid the CDP control-channel tells this page describes, so a request gets JavaScript rendering without the automation channel giving away the session the way an unpatched Puppeteer or Playwright default configuration does.
Glossary
FAQ
No — it means their default configuration leaves tells behind. Patched forks and configuration changes exist to avoid enabling the leaky protocol domains in the first place, at which point this detection method has nothing left to probe.
No. navigator.webdriver is a static property a script can simply delete or spoof. CDP detection probes dynamic side effects of the control channel itself — like a getter that only fires when a specific protocol domain is enabled — which isn't fixed by editing one JavaScript property.
Yes — Selenium's Chromium driver also runs over CDP under the hood since ChromeDriver adopted it, so the same class of leak applies, on top of Selenium's own historical cdc_-prefixed variable tells from the WebDriver protocol layer.
Beyond CDP Detection (Runtime.enable Leak), Crawlora's own docs cover the rest of the stack — browse the APIs, test a request in Playground, and move from scraping infrastructure work to production data workflows.