Как сайты обнаруживают Puppeteer, Playwright и Selenium через побочные эффекты CDP, включая утечку сериализации при Runtime.enable.
Puppeteer, Playwright и значительная часть современного Selenium управляют Chrome через один и тот же низкоуровневый канал: Chrome DevTools Protocol (CDP) — тот самый интерфейс, который используют сами Chrome DevTools. Эта общая основа удобна для авторов инструментов автоматизации, но она же означает, что все эти инструменты могут оставлять одну и ту же группу следов — независимо от того, подделаны ли строка User-Agent или navigator.webdriver. В этом руководстве объясняется, что такое CDP, какие конкретно побочные эффекты он создаёт и как системы детекции превращают эти эффекты в сигнал.
Ключевые выводы
- CDP — это общий канал управления для Puppeteer, Chromium-драйвера Playwright и CDP-сессий Selenium: детектор, распознающий сам CDP, ловит все три инструмента разом.
- Включение домена Runtime даёт наблюдаемый побочный эффект: как только клиент отправляет
Runtime.enable, каждый вызовconsole.*перехватывается, а его аргументы сериализуются для предпросмотра — это принудительно вызывает геттеры свойств, к которым страница иначе никогда бы не обратилась. - Этот побочный эффект сериализации — готовый примитив для детекции: страница может определить геттер на значении, передаваемом в
console.log, и проверить, срабатывает ли он немедленно, — раскрывая подключённый CDP-клиент без единого видимого сообщения в консоли. - Инструменты различаются по степени уязвимости: Puppeteer и Chromium-режим Playwright по умолчанию включают
Runtime.enableдля отслеживания execution context; патченные сборки обходятся без этого, управляя идентификаторами контекста иначе. - Это один сигнал в многослойной системе, а не серебряная пуля — он дополняет проверки
navigator.webdriverи аномалий рендеринга, описанные в нашем руководстве по обнаружению headless-браузеров.
Что такое CDP и почему автоматизация от него зависит
Chrome DevTools Protocol — это JSON-RPC интерфейс, который позволяет внешнему клиенту инспектировать и управлять запущенным экземпляром Chromium: выполнять JavaScript, перехватывать сетевые запросы, делать скриншоты и получать события консоли и исключений. Это тот же протокол, на котором сами DevTools общаются с браузером, когда вы открываете их вручную.
Puppeteer и Chromium-бэкенд Playwright подключаются через CDP напрямую. Selenium традиционно использовал проводной протокол W3C WebDriver, но реализация WebDriver в Chromium (chromedriver) сама по себе является CDP-клиентом под капотом, а специфичный для Chromium API DevTools в Selenium 4 напрямую предоставляет доступ к CDP. Поскольку все три инструмента в конечном счёте разделяют один и тот же уровень управления, детектор, нацеленный на механику CDP, а не на артефакт конкретного фреймворка, способен поймать все три сразу.
Сигналы CDP, доступные странице
У веб-страницы нет прямого API, чтобы спросить «подключён ли CDP-клиент?». CDP спроектирован так, чтобы быть невидимым для JavaScript страницы. Но подключение клиента меняет то, что происходит внутри рендерера способами, которые страница может измерить косвенно: некоторые домены протокола начинают отправлять события только по запросу клиента, а обработка этих событий выполняет дополнительный код в том же потоке JavaScript, где исполняется страница. Самым полезным из таких эффектов оказывается тот, что возникает при включении домена Runtime.
Runtime.enable и утечка через сериализацию консоли
Клиенты автоматизации отправляют Runtime.enable, чтобы получать события execution context и вызовов console API — Puppeteer и Playwright нужно это, чтобы знать, в каком ID контекста выполнять скрипты. Как только этот домен активен, инспектор V8 перехватывает каждый вызов console.* до того, как он достигнет видимой консоли, и строит предпросмотр объекта для соответствующего события CDP — независимо от того, открывает ли человек DevTools, чтобы это увидеть.
Построение предпросмотра означает обход свойств аргументов, а обход свойств означает вызов их геттеров. На неинструментированной странице геттер на объекте, залогированном через console.log, срабатывает только если человек реально открыл DevTools и смотрит в консоль. При активном Runtime.enable он срабатывает безусловно и немедленно — как побочный эффект самого конвейера событий CDP:
// Выполняется немедленно (до появления какого-либо вывода
// где бы то ни было) только когда у CDP-клиента включён домен Runtime.
let cdpDetected = false;
const probe = {};
Object.defineProperty(probe, 'flag', {
get() {
cdpDetected = true;
return 'x';
},
});
console.log(probe);
// Проверьте cdpDetected на следующем тике — видимая консоль не нужна.
Это скорее временной/сериализационный побочный канал, а не документированный API: он зависит от того, как инспектор V8 строит предпросмотры консоли, а это деталь реализации, а не стабильный контракт. Именно поэтому этот приём никогда не был постоянным изъяном — поведение на уровне движка вокруг именно этого перехвата со временем менялось по мере изменений в том, как Chromium обрабатывает аргументы консоли.
Следы Puppeteer и Playwright
Оба фреймворка по умолчанию включают домен Runtime, и именно это делает технику широко эффективной против скриптов «из коробки», а не редким краевым случаем.
| Инструмент | Поведение CDP по умолчанию | Распространённое смягчение |
|---|---|---|
| Puppeteer | Отправляет Runtime.enable для отслеживания execution context | Форки сообщества избегают постоянного включения домена, восстанавливая ID контекста иным способом |
| Playwright (Chromium) | Включает Runtime для каждого execution context в своём дефолтном page world | Патчи сообщества выполняют скрипты автоматизации в изолированных world, полученных без Runtime.enable |
Selenium (chromedriver / W3C) | Преимущественно использует проводной протокол W3C WebDriver; использование CDP более узкое и опциональное | Отказ от API DevTools полностью обходит именно эту проверку |
Практический итог: детектор, построенный вокруг этого единственного механизма, когда-то отделял сессии Puppeteer/Playwright по умолчанию от обычных сессий Selenium на базе WebDriver — пока авторы автоматизации не изменили способ управления CDP в своих инструментах.
Эвристики детекции против обхода
Это та же динамика кошки-мышки, что описана в нашем руководстве по техникам обнаружения ботов, только на уровень ниже по стеку. Как только побочный канал Runtime.enable стал известен, он быстро распространился среди поставщиков bot-management, потому что ловил несколько фреймворков одной проверкой и не зависел от патчей JavaScript-уровня, которые уже покрывали stealth-плагины. Разработчики инструментов автоматизации в ответ выпустили сборки, которые пропускают Runtime.enable или иначе отслеживают execution context, закрыв эту конкретную дыру, сохранив остальное поведение фреймворка неизменным — та же картина видна в давнем issue на GitHub для Puppeteer, где годами отслеживают сообщения об обнаружении автоматизации в этой библиотеке.
Ни одна из сторон не удерживает преимущество надолго. Сигнал, зависящий от недокументированных внутренностей инспектора, — это именно то, что изменение движка может незаметно сломать или восстановить, поэтому серьёзные системы детекции рассматривают его как один вход в более крупную оценку — наряду с navigator.webdriver, аномалиями рендеринга WebGL и сетевыми отпечатками, описанными в других статьях этого сайта, — а не как самостоятельный вердикт.
Проверьте собственную настройку автоматизации
Если вы поддерживаете браузерные тесты CI, аудиты доступности или скрипты мониторинга, стоит знать, что система детекции видит в ваших сессиях. Инструмент обнаружения ботов от BrowserInsight показывает navigator.webdriver, headless-артефакты и другие сигналы автоматизации, которые сейчас выдаёт ваш браузер, а проверка ядра рендеринга подтверждает, какой движок рендеринга реально работает под капотом. Ни один из этих инструментов не инспектирует трафик протокола CDP напрямую — такая инспекция происходит на стороне сервера, — но они показывают смежные сигналы, которые реальная система детекции объединила бы с ним.
Часто задаваемые вопросы
Может ли сайт напрямую обнаружить, что подключён CDP?
Не через документированный API — CDP спроектирован так, чтобы быть невидимым для JavaScript страницы. Страница может обнаружить побочные эффекты активности конкретных доменов CDP, например поведение сериализации консоли, которое проявляется при включённом домене Runtime.
Влияет ли это на Selenium так же, как на Puppeteer и Playwright?
Не так же последовательно. Классический путь Selenium работает поверх проводного протокола W3C WebDriver, а не напрямую через CDP, поэтому он не запускает побочные эффекты доменов CDP автоматически так, как это делают конфигурации Chromium по умолчанию в Puppeteer и Playwright. Сессии Selenium, явно использующие API DevTools Chromium, всё же могут вызывать те же проверки.
Было ли включение Runtime.enable ошибкой авторов инструментов автоматизации?
Нет — это простейший способ получать события execution context и консоли через CDP, и именно это делают сами DevTools, когда их открывает человек. Побочный эффект становится сигналом бота лишь потому, что у скрипта, в отличие от человека, нет иной причины иметь подключённый CDP-клиент в продакшене.
Устраняет ли исправление этого одного сигнала возможность обнаружения автоматизации?
Нет. Это лишь убирает один вход из оценки детекции, которая также включает несогласованность рендеринга, поведенческий анализ и фингерпринтинг сетевого уровня — см. наши руководства по обнаружению headless-браузеров и техникам обнаружения ботов, чтобы узнать об остальной части этой системы.


