Anti-Bot

What Is Chrome DevTools Protocol (CDP) Detection?

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). Playwright, Puppeteer, and Selenium 4 all control Chrome via CDP, and enabling certain CDP domains - especially Runtime - changes observable behaviour inside the page. The best-known tell is the Runtime.enable serialization side effect, probed via a getter on a logged object. 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 Runtime.enable, Chrome starts forwarding console activity to the controller. To do that, it must serialize the arguments passed to console.log and friends so they can cross the protocol boundary. That serialization is observable: if you log an object that has a getter 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 see 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 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 ways:

  • Console.enable - similar to Runtime; turning it on changes how console objects are handled and can be probed.
  • Framework bindings - Playwright injects window.__playwright__binding__ / __pwInitScripts; older Puppeteer and ChromeDriver leave their own globals (cdc_ properties). These are exposed-function artifacts of the CDP control channel.
  • Timing and event anomalies - CDP-dispatched input events can lack the trusted-event properties and natural timing of real user input.
  • toString inspection - any JS patch a CDP driver injects to hide itself is itself inspectable.

The common thread is that the control channel and the scripts it injects exist in or affect page scope, and a sufficiently paranoid anti-bot script can find them.

Reducing the CDP surface

Mitigations, roughly in order of robustness:

  1. Do not enable Runtime/Console unless needed, and delete framework bindings in an init script (delete window.__playwright__binding__; delete window.__pwInitScripts;). This removes the cheapest tells.
  2. Hook earlier / lower - run automation logic in a privileged context before navigation rather than injecting into page 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 at the C++ level, so Runtime.enable no longer changes page-observable behaviour.

The reason CDP detection matters so much is the same theme that runs through 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 specifically 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. Puppeteer, Playwrig…
What Is Function.toString() Inspection?
Function.prototype.toString() inspection is the technique anti-bot scripts use to detect runtime JavaScript patches. Every JS function expos…
What Is navigator.webdriver?
navigator.webdriver is a standardized boolean that returns true when the browser is being controlled by automation. It is defined by the W3C…
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…
What Is Browser Fingerprinting Evasion?
Browser fingerprinting evasion is the practice of configuring an automated browser so that the combined fingerprint it presents — canvas, We…
What Is Kasada?
Kasada is a gatekeeper-proxy bot defense used by major retailers, ticketing platforms, and sneaker drops. Unlike Cloudflare or DataDome, it …
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 distinguish automated traffic from human users — and to block, challenge, or thr…
Anti-Bot Vendor Detection Cheatsheet
The first step of any scrape against a protected site is identifying which anti-bot vendor is in front of it. The vendor determines almost e…
How Do Websites Detect Web Scrapers?
Websites detect scrapers by collecting hundreds of signals across the network, transport, browser, and behavioral layers, then scoring the c…

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 they use. The main tell is the Runtime.enable serialization side effect - logging an object with a getter and watching it fire. They also look for framework bindings like window.__playwright__binding__ and ChromeDriver cdc_ globals, and for CDP-dispatched input events that lack real-user timing and trusted-event flags.

Does deleting window.__playwright__binding__ make Playwright undetectable?

It removes one cheap tell, not all of them. The Runtime.enable serialization trap, console handling changes, input-event anomalies, and any injected stealth patches (via toString inspection) remain. Reducing the CDP surface requires 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?

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 CDP-based tools.

Last updated: 2026-05-30