Anti-Bot

What Is Chrome DevTools Protocol (CDP) Detection?

By the Scrappey Research Team

What Is Chrome DevTools Protocol (CDP) Detection? — conceptual illustration
On this page

CDP detection is the family of techniques anti-bot scripts use to tell that a browser is being driven through the Chrome DevTools Protocol (CDP). CDP is the remote-control channel that automation tools use to drive Chrome - they send it commands like "navigate" or "run this JavaScript." Playwright, Puppeteer, and Selenium 4 all control Chrome via CDP, and switching on certain CDP features - especially the Runtime domain (the part that handles JavaScript and the console) - changes behaviour that a page can observe in JavaScript. The best-known tell is the Runtime.enable serialization side effect, probed via a getter on a logged object (explained below). Because these signals come from the automation channel itself, they catch CDP-driven scrapers even when every static fingerprint looks perfect.

Quick facts

TargetBrowsers driven via Chrome DevTools Protocol (Playwright/Puppeteer/Selenium 4)
Flagship tellRuntime.enable causes the page to serialize console-logged objects
The probeLog an object with a getter; if the getter fires, CDP is listening
Also leaksConsole.enable, and framework binding artifacts in page scope
MitigationAvoid enabling Runtime/Console; drive below CDP or patch the engine

The Runtime.enable serialization tell

When an automation client sends the Runtime.enable command, Chrome starts forwarding console activity back to the controller. To do that, it has to serialize the arguments passed to console.log and similar calls — that is, convert them into data that can cross the protocol boundary (the gap between the page and the tool driving it). That conversion is observable: if you log an object that has a getter (a function that runs when a property is read) on one of its properties, the act of serializing the object invokes the getter — even though no human ever opened DevTools.

So the probe is tiny: create an object whose id (or toString, or a numeric coercion) is a getter, console.log it, and check whether the getter ran. On a normal browser nothing reads the property and the getter never fires. Under CDP with Runtime.enable active, the getter fires — revealing the automation channel. This is the same kind of Error.stack-style trap that catches frameworks which keep Runtime enabled by default.

Other CDP leaks

Beyond Runtime.enable, CDP-driven automation leaks in several other ways:

  • Console.enable - similar to Runtime; turning it on changes how console objects are handled, which a page can probe for.
  • Framework bindings - the tools leave their own fingerprints in the page. Playwright injects window.__playwright__binding__ / __pwInitScripts; older Puppeteer and ChromeDriver leave their own global variables (cdc_ properties). These are leftover artifacts of the CDP control channel exposing functions to the page.
  • Timing and event anomalies - mouse and keyboard events dispatched through CDP can lack the trusted-event properties and natural timing of input from a real person.
  • toString inspection - any JavaScript a CDP driver injects to hide itself can itself be inspected and exposed.

The common thread: the control channel, and the scripts it injects, live in or affect the page's own scope — so a sufficiently paranoid anti-bot script can find them.

Reducing the CDP surface

Ways to shrink these tells, roughly from easiest to most robust:

  1. Do not enable Runtime/Console unless you actually need them, and delete the framework bindings in an init script that runs before the page loads (delete window.__playwright__binding__; delete window.__pwInitScripts;). This removes the cheapest tells.
  2. Hook earlier / lower - run your automation logic in a privileged context before the page navigates, rather than injecting code into the page's scope, so there is less for the page to observe.
  3. Patch the engine - anti-detect browsers suppress the console serialization side effect and hide the bindings down in the C++ source, so Runtime.enable no longer changes anything the page can see.

The reason CDP detection matters so much is the same theme that runs through all fingerprinting: a perfect static fingerprint does not help if the way you are driving the browser betrays you. Real-browser drivers and managed APIs that minimise the CDP footprint exist precisely because the control channel is its own detection surface.

Code example

javascript
// The Runtime.enable serialization trap (paraphrased)
let cdpDetected = false;

const trap = {};
Object.defineProperty(trap, 'id', {
  get() { cdpDetected = true; return 'x'; }  // fires only if something serializes us
});

console.log(trap);   // human browser: nobody reads .id, getter never runs
                     // CDP + Runtime.enable: Chrome serializes the object -> getter fires

setTimeout(() => {
  if (cdpDetected) {
    // Automation channel is listening - block-grade even if the fingerprint is clean.
  }
}, 0);

// Related cheap checks the same script runs:
//   'window.__playwright__binding__' in window   -> Playwright
//   /\$?cdc_/.test(Object.keys(document).join()) -> ChromeDriver

Related terms

What Is the Chrome DevTools Protocol (CDP)?
The Chrome DevTools Protocol (CDP) is the low-level interface for instrumenting and controlling Chromium-based browsers. Low-level means it …
What Is Function.toString() Inspection?
Function.prototype.toString() inspection is a technique anti-bot scripts use to identify JavaScript functions that have been modified at run…
What Is navigator.webdriver?
navigator.webdriver is a standardized boolean that returns true when the browser is being controlled by automation. Think of it as a built-i…
What Is Headless Browser Detection?
Headless browser detection is the set of probes anti-bot systems use to distinguish a headless or instrumented Chrome session from a real us…
How Browser Fingerprinting Works
Browser fingerprinting is how a site combines signals — canvas, WebGL, audio, fonts, navigator probes, TLS (the encryption layer behind http…
What Is Kasada?
Kasada is a bot-defense system that big retailers, ticketing sites, and sneaker drops put in front of their servers to manage automated traf…
What Is Browser Fingerprinting?
Browser fingerprinting is a technique that identifies and tracks a visitor by combining dozens of small, observable characteristics of their…
What Is Anti-Bot Detection?
Anti-bot detection is the set of techniques websites use to tell automated traffic apart from real human visitors — and then block, challeng…
Anti-Bot Vendor Detection Cheatsheet
A useful first step when working with any protected site you are authorized to access is identifying which anti-bot vendor sits in front of …
How Do Websites Detect Web Scrapers?
Websites spot scrapers by gathering hundreds of small clues about each visitor, then scoring how human the whole picture looks. No single cl…
How Do You Instrument a Browser to Study Anti-Bot Scripts?
Instrumenting a browser means adding observation points so you can watch exactly which APIs a page calls -- which is how researchers study f…
How Does toString() Reveal a Hooked Function?
Calling toString() on a native browser function returns a fixed marker -- "function name() { [native code] }" -- while a JavaScript wrapper …
What Is rebrowser-patches?
rebrowser-patches is an open-source set of drop-in patches for Puppeteer and Playwright that changes how those libraries set up their CDP ex…
What Is nodriver?
nodriver is an open-source, asynchronous Python library that drives Chrome directly over the Chrome DevTools Protocol (CDP), with no Seleniu…
What Is undetected-chromedriver?
undetected-chromedriver is an open-source Python library that provides a patched version of Selenium's ChromeDriver. It is a near drop-in re…

Concept map

How CDP Detection connects

The terms most directly tied to this one. Hover a node to see its neighbours, click to preview, drag to rearrange.

0 terms · 0 connections
You are here · Anti-Bot
Building map…

Frequently asked questions

How do anti-bots detect Playwright or Puppeteer specifically?

Through the CDP control channel these tools use to drive Chrome. The main tell is the Runtime.enable serialization side effect — logging an object with a getter and watching the getter fire. Anti-bots also look for framework bindings like window.__playwright__binding__ and ChromeDriver cdc_ globals, and for CDP-dispatched mouse/keyboard events that lack the timing and trusted-event flags of real user input.

Does deleting window.__playwright__binding__ hide all of Playwright's CDP signals?

No — it removes one cheap tell, not all of them. The Runtime.enable serialization trap, console handling changes, input-event anomalies, and any injected patches (exposed via toString inspection) all remain. Reducing the CDP surface means not enabling Runtime/Console, hooking earlier, or using an engine that suppresses the side effects at the C++ level.

Is Selenium detected the same way as Playwright?

Largely yes. Selenium 4 drives Chrome over CDP too, so it shares the Runtime.enable family of tells. Classic Selenium also leaves ChromeDriver artifacts (cdc_ properties) and sets navigator.webdriver. The control-channel signals are broadly the same across all CDP-based tools.

Last updated: 2026-05-31