---
title: "Chrome DevTools Protocol (CDP): can a site detect it?"
description: "What the Chrome DevTools Protocol is, what a driver does with it, what a page notices, from a tab at 0 frames per second to a click, and why header order is lost."
canonical: "https://scalebrowser.net/blog/chrome-devtools-protocol"
last_modified: "2026-10-26"
published: "2026-10-26"
author: "davide"
category: "infrastructure"
---

> ## Documentation Index
> Fetch the complete documentation index at: https://scalebrowser.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Chrome DevTools Protocol (CDP): can a site detect it?

> What the Chrome DevTools Protocol is, what a driver does with it, what a page notices, from a tab at 0 frames per second to a click, and why header order is lost.

Every Chromium automation tool you have used talks to the browser the same way underneath, whether it calls itself a test runner, a scraper or an agent. The protocol is not the part that sites detect, but the way a tool uses it is, and some of the traces sit in places nobody thinks of as a fingerprint: which tab is in front, how a new tab was opened, what a click was preceded by. CDP here means the Chrome DevTools Protocol, not the Cisco Discovery Protocol that shares the abbreviation.

## What is the Chrome DevTools Protocol?

The Chrome DevTools Protocol is the message protocol through which a client inspects and controls a Chromium browser: JSON commands, answers and events, exchanged over a WebSocket. Chrome's own DevTools panel is one client of it; Puppeteer, Playwright's Chromium driver and nodriver are others. The protocol is split into domains, each a group of commands the client sends and events the browser pushes back, such as `Page`, `Network` or `Input`.

Every command is addressed to a target. A target is anything the browser can be attached to: a tab, an iframe running in its own process, a worker, a service worker, or the browser itself. A client attaches to a target, receives a session, and sends that session's commands. On 27 September 2026 the tip-of-tree definition, which Chromium's repository regenerates daily, counted these sizes:

| Part of the protocol | Count |
| --- | --- |
| Domains | 58 |
| Commands | 670 |
| Events | 232 |

Several domains are marked experimental and can change between Chrome releases, and the protocol viewer publishes a stable version next to tip-of-tree.

## What can a driver do over CDP?

A driver can do almost anything a person at the keyboard can do, and several things nobody at a keyboard could. Five domains carry most of the work of an automation tool:

- **`Page`** loads and captures the page: `Page.navigate` opens an address, `Page.captureScreenshot` returns a picture of it.
- **`Runtime`** runs JavaScript inside it: `Runtime.evaluate` "evaluates expression on global object", in the protocol's own words.
- **`Input`** synthesises the user: `Input.dispatchMouseEvent` "dispatches a mouse event to the page", `Input.insertText` inserts text as an emoji keyboard or an input method would.
- **`Network`** watches and shapes traffic: every request and response arrives as an event, and `Network.setExtraHTTPHeaders` adds headers to every request.
- **`Target`** manages tabs and frames: `Target.createTarget` opens a page, `Target.attachToTarget` connects to one, `Target.activateTarget` brings it to the front.

A sixth domain matters for agents. `Accessibility.getFullAXTree` "fetches the entire accessibility tree for the root Document", which is the raw material of every page map an agent reads instead of the HTML. How Claude Code uses a real browser, with the tokens per step compared, is covered in Claude Code with a real browser.

## What can a page notice about CDP?

A page cannot see the protocol, but it can see what the protocol makes the browser do, and the first trace is the tab itself. A tab a driver merely attaches to stays a background tab: it reports `visibilityState` hidden, draws 0 animation frames per second and throttles its timers to about 1 per second. A person cannot type into a tab in that state, and any page can read all three values. We measured it on 10 August 2026 with two tabs of the same origin, reading only what each page saw, in our engine and in plain Chromium of the same version, with and without a window.

![A CDP driver sends JSON over a WebSocket to two tabs: the attached-only tab reports hidden at 0 frames per second, the activated tab visible at 59.8 to 60.4, and a page script reads both.](/blog/chrome-devtools-protocol/attached-tab.png "A page cannot see the protocol, only what it makes the browser do: an attached-only tab stays hidden at 0 frames per second, in our engine and plain Chromium alike.")

<Evidence source="Scalebrowser tab probe, our engine and plain Chromium of the same version, headless and headed, 12 runs, 10 August 2026">

| How the driver takes the tab | Target tab reports | Animation frames per second | Timer callbacks per second | Tabs that believe they have focus |
| --- | --- | --- | --- | --- |
| attach only | hidden | 0 | 0.6 to 1.2 | 1, the other tab |
| attach, then `Target.activateTarget` | visible | 59.8 to 60.4 | 10 | 1 |
| attach, then `Page.bringToFront` | visible | 59.8 to 60.4 | 10 | 1 |

</Evidence>

Both engines answered identically, so this is how Chromium behaves, not a defect of one build: the bound tab has to be the front tab, and activating it produces the ordinary `blur`, `visibilitychange` and `focus` sequence a person switching tabs would. How a tab was opened is visible too. A tab created over CDP arrives in front with no `Referer` and `Sec-Fetch-Site: none`, like an address typed by hand; a middle click on a link arrives in the background with the referrer set and `same-origin`.

The second trace is the input. Reddit ships 9 `platform_health` checks in its page bundle, 18,475 bytes in all, and we ran them verbatim on 8 September 2026 against three setups on one Windows machine: a person, a Scalebrowser profile, and a bare driver sending CDP input events.

<Evidence source="Reddit's 9 platform_health checks lifted verbatim from its bundle, one Windows machine, 8 September 2026">

| Setup | Automation markers | Clicks without a usable pointer path |
| --- | --- | --- |
| A person | none | 4 of 23 |
| Scalebrowser profile, [headless](/blog/headless-browser) | none | 1 of 6, still with 2 pointer samples |
| Bare CDP driver, headless | flagged | 4 of 4, with no pointer samples at all |

</Evidence>

The table corrects a first reading of our own. A click with no path before it looked like a driver's signature until the person's run produced four, two of them with exactly the empty value the bare driver produced on every click: a person clicking twice on the same spot does not move the mouse in between. The bare driver's markers came from how it was launched, headless and with the automation switch, not from the protocol itself.

Five of the nine checks answered the same for the person and the profile, down to individual fields, and one check that aims at drivers stayed silent for all three setups, so a clean result on it says nothing about a tool. The one difference between the person and the profile was statistical: the profile's pointer paths varied less from click to click, with every single value inside the person's range.

The guide to the [AI agent browser](/blog/ai-agent-browser) sets these traces next to the three other layers a site reads, and the [direct CDP documentation](/docs/cdp) shows how to connect your own driver to a profile. How several browser profiles run on one machine is covered in [Multiple browser profiles on one machine](/blog/browser-profiles-fleet).

## Why can CDP not read header order?

CDP cannot read header order because it reports request headers as a JSON object keyed by header name, and the order is gone before the client sees it. The `Headers` type in the [protocol definition](https://github.com/ChromeDevTools/devtools-protocol/blob/master/json/browser_protocol.json) reads "Request / response headers as keys / values of JSON object", and the older text form of request headers is marked deprecated, "replaced by the headers in Network.requestWillBeSentExtraInfo", whose headers are an object again. [RFC 8259](https://www.rfc-editor.org/rfc/rfc8259) defines a JSON object as "an unordered collection of zero or more name/value pairs" and notes that parsing libraries differ in whether they keep member order at all. A site's server, by contrast, sees the bytes in the order they were sent, which is why header order, like the TLS fingerprint in our JA4 article, can only be measured at a server that reads the raw request.
