CDP-утечки в Puppeteer: как антифрод определяет автоматизацию через Chrome DevTools Protocol
02.06.2026


Markus_automation
Expert in data parsing and automation
Среди инструментов автоматизации Puppeteer исторически занимает особое место. В отличие от тяжеловесного Selenium, он предложил разработчикам нативный и быстрый контроль над Chromium из коробки в среде Node.js.
Puppeteer стал стандартом индустрии благодаря колоссальной экосистеме готовых плагинов и глубокой интеграции с движком V8. Вы можете взять Puppeteer, подключить к нему puppeteer-extra-plugin-stealth, купить качественные прокси, аккуратно подменить отпечатки Canvas и WebGL — и для многих задач этого будет достаточно.
Но на хорошо защищенных сайтах подобный парсер может столкнуться с проблемами. Системы защиты вроде Cloudflare, Akamai или DataDome начнут блокировать ваши сессии. Почему это происходит?
Проблема кроется не в логике вашего кода или качестве прокси. Причина заложена в самом фундаменте, который делает Puppeteer таким мощным и удобным — в Chrome DevTools Protocol (CDP). Этот низкоуровневый протокол управления оставляет специфические цифровые следы, которые современные антифрод-алгоритмы умеют читать и учитывать при выявлении ботов.
Давайте разберемся в механике этих утечек и выясним, почему поверхностные методы маскировки стандартного Puppeteer больше не работают.
Содержание
Сохраняйте анонимность с Octo Browser, ведь отследить ваш реальный цифровой отпечаток невозможно.
Что такое CDP и почему он оставляет цифровые следы
Chrome DevTools Protocol предоставляет низкоуровневый доступ к архитектуре браузера. Изначально он создавался для отладки, инспектирования и профилирования, но не для скрытного парсинга.
Протокол функционирует через WebSocket-соединения, напрямую взаимодействуя с движком V8. Когда ваш скрипт отправляет команду (например, клик или переход по ссылке), браузер открывает локальный сокет для двустороннего обмена данными. Этот процесс неизбежно формирует внутренние логи, меняет распределение памяти и оставляет артефакты в изолированной среде браузера. Защитные механизмы способны анализировать эти микроизменения и тайминги для выявления машинного поведения. Следовательно, сам факт поддержания соединения уже демаскирует вашу автоматизацию.
В чем отличие CDP-утечек от стандартного фингерпринтинга
Важно понимать, что CDP-утечки и утечки через отпечатки браузера — это два принципиально разных вектора обнаружения.
Классический отпечаток (Canvas, WebGL, Fonts): идентифицирует уникальное оборудование и особенности рендеринга видеокарты. Несовершенство отпечатка или рассинхрон данных позволяют антибот-системам выявлять подмену и маркировать пользователя как бота.
CDP-утечки: раскрывают поведенческие и структурные маркеры исполнения кода. Они показывают сам факт удаленного управления браузером.
У вас может быть уникальный отпечаток WebGL, но флаги автоматизации сведут эту уникальность к нулю. Если проверка отпечатка браузера требует ресурсозатратных операций и времени, то проверка переменных среды или стека вызовов не стоит практически ничего. Именно поэтому антифрод-системам выгодно использовать CDP-детекты. Они позволяют отсекать ботов на этапе базовых протокольных проверок, экономя серверные мощности на глубоком поведенческом анализе.
Анатомия утечек через Chrome DevTools Protocol
Маркеры контекста исполнения (Execution Context)
Главная слабость Puppeteer в том, как именно он заставляет браузер выполнять ваш код. Под капотом метод page.evaluate() использует CDP-команду Runtime.evaluate.
При передаче скрипта в браузер современные версии Puppeteer формируют специфический Source URL, который привязывается к этому куску кода. Если в процессе выполнения скрипта возникает ошибка, движок V8 формирует стандартный стек вызовов, и там появляется примерно следующее:
at pptr:evaluate;C:\Users\Admin\Projects\...\main.js:17:14
at pptr:evaluate;C:\Users\Admin\Projects\...\main.js:17:14
Антифрод, переопределяя базовые функции, проактивно провоцирует невидимые ошибки и читает объект Error.stack. Он видит там:
Префикс
pptr:— прямую аббревиатуру Puppeteer.Абсолютный путь к файлу на вашем локальном диске или сервере (начинающийся с
C:\или/var/www/...).
Браузер реального пользователя, находясь на публичном сайте, никогда не исполняет код с локальных путей файловой системы, соответственно, подобные флаги — это стопроцентный маркер автоматизации.

Эту проблему не решает смена IP-адреса или подмена Canvas. Чтобы скрыть этот маркер, вам придется вмешаться либо в исходники библиотеки, либо в среду исполнения браузера.
Самый надежный способ — вырезать маркер pptr:evaluate из исходного кода библиотеки Puppeteer до того, как она отправит команду в браузер. Искать и править файлы руками после каждого npm install нерационально, поэтому лучше написать простой патчер:
const fs = require('fs'); const path = require('path'); // Путь к файлу ExecutionContext.js в новых версиях Puppeteer const targetFile = path.resolve(__dirname, 'node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/ExecutionContext.js'); if (fs.existsSync(targetFile)) { let content = fs.readFileSync(targetFile, 'utf8'); // Заменяем префикс pptr:evaluate на анонимный вызов // и обрезаем локальный путь к файлу const patchedContent = content.replace(/pptr:evaluate;.*?\\n/g, 'anonymous:evaluation;\n'); fs.writeFileSync(targetFile, patchedContent, 'utf8'); console.log('Puppeteer пропатчен! Маркеры pptr:evaluate удалены.'); }
const fs = require('fs'); const path = require('path'); // Путь к файлу ExecutionContext.js в новых версиях Puppeteer const targetFile = path.resolve(__dirname, 'node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/ExecutionContext.js'); if (fs.existsSync(targetFile)) { let content = fs.readFileSync(targetFile, 'utf8'); // Заменяем префикс pptr:evaluate на анонимный вызов // и обрезаем локальный путь к файлу const patchedContent = content.replace(/pptr:evaluate;.*?\\n/g, 'anonymous:evaluation;\n'); fs.writeFileSync(targetFile, patchedContent, 'utf8'); console.log('Puppeteer пропатчен! Маркеры pptr:evaluate удалены.'); }
Если нет возможности исправить node_modules, маркеры можно вырезать на лету, внедрив маскировочный скрипт до загрузки страницы сайта. Он переопределяет базовое поведение объекта Error и фильтрует стек вызовов:
await page.evaluateOnNewDocument(() => { // Сохраняем оригинальный класс Error const NativeError = window.Error; window.Error = function(...args) { const err = new NativeError(...args); const originalStack = err.stack; if (originalStack) { Object.defineProperty(err, 'stack', { get: function() { // Разбиваем стек на строки и вырезаем маркер Puppeteer return originalStack .split('\n') .filter(line => !line.includes('pptr:evaluate')) .join('\n'); } }); } return err; }; // Восстанавливаем цепочку прототипов, чтобы она не выдала нас на проверке window.Error.prototype = NativeError.prototype; });
await page.evaluateOnNewDocument(() => { // Сохраняем оригинальный класс Error const NativeError = window.Error; window.Error = function(...args) { const err = new NativeError(...args); const originalStack = err.stack; if (originalStack) { Object.defineProperty(err, 'stack', { get: function() { // Разбиваем стек на строки и вырезаем маркер Puppeteer return originalStack .split('\n') .filter(line => !line.includes('pptr:evaluate')) .join('\n'); } }); } return err; }; // Восстанавливаем цепочку прототипов, чтобы она не выдала нас на проверке window.Error.prototype = NativeError.prototype; });

В итоге вместо локальных путей будет выводиться примерно такой текст
Уязвимости метода Page.addScriptToEvaluateOnNewDocument
Попытки скрыть автоматизацию (включая использование популярных stealth-плагинов) чаще всего сводятся к инъекции маскировочного JavaScript до загрузки страницы целевого сайта. В Puppeteer это делается через команду Page.addScriptToEvaluateOnNewDocument.
Сам факт использования этого метода оставляет четкие следы в среде исполнения.
Аномалии таймингов: команда заставляет движок V8 синхронно выполнить ваш код в момент создания контекста страницы, до того как HTML-парсер начнет свою работу. Внедрение объемных скриптов создает ощутимые микрозадержки на этапе
document_start. Антифрод измеряет время между внутренними событиями браузера и видит неестественный временной провал.Нарушение жизненного цикла: stealth-плагины не могут просто стереть флаги автоматизации. Они используют сложные перехватчики. Защитный механизм выявляет, что сложные прокси-объекты и переопределенные свойства появились в объекте
windowаномально рано — до событияDOMContentLoadedили даже до парсинга тега<head>. Это нарушает естественный жизненный цикл страницы.Отсутствие изоляции: любые скрипты, внедренные через CDP-команду
addScriptToEvaluateOnNewDocument, выполняются в главном контексте страницы. Это значит, что код вашего маскировочного плагина и скрипты антифрод-системы сайта оказываются в одном пространстве. Малейший баг или случайно оставленная переменная — и защита сайта их увидит.
Отказаться от маскировки нельзя, но можно изменить способ доставки маскировочного кода. Вместо того чтобы вбрасывать код через протокол отладки, можно использовать нативный механизм самого браузера — загрузку собственного расширения.
Браузеры проектировались так, чтобы расширения могли безопасно внедрять код. Если упаковать логику маскировки в расширение Manifest V3 и загрузить его при старте Puppeteer, антифрод воспримет эти тайминги и инъекции как работу обычного пользовательского аддона.
Например, вы можете создать папку stealth-extension с файлом manifest.json:
{ "manifest_version": 3, "name": "My Custom Stealth", "version": "1.0", "content_scripts": [ { "matches": ["<all_urls>"], "js": ["inject.js"], "run_at": "document_start", "world": "MAIN" } ] }
{ "manifest_version": 3, "name": "My Custom Stealth", "version": "1.0", "content_scripts": [ { "matches": ["<all_urls>"], "js": ["inject.js"], "run_at": "document_start", "world": "MAIN" } ] }
И подключить его при запуске Puppeteer вместо вызова evaluateOnNewDocument:
const browser = await puppeteer.launch({ args: [ `--disable-extensions-except=${pathToExtension}`, `--load-extension=${pathToExtension}` ] });
const browser = await puppeteer.launch({ args: [ `--disable-extensions-except=${pathToExtension}`, `--load-extension=${pathToExtension}` ] });
Второй способ — вообще не использовать механизмы V8 для инъекции. Вы можете использовать внешний прокси-сервер или встроенный интерцептор запросов Puppeteer, чтобы модифицировать сырой HTML-ответ от сервера на лету.
Вставьте свой тег <script> с маскировкой в самую первую строку тега <head>. В этом случае код выполнится абсолютно естественным образом в рамках стандартного парсинга документа, не вызывая подозрений у счетчиков таймингов.
Если нужно обязательно использовать addScriptToEvaluateOnNewDocument, откажитесь от загрузки монолитных stealth-плагинов. Разделите маскировку на два этапа.
Перед загрузкой страницы избавьтесь только от маркера webdriver. Этот код выполняется за доли миллисекунды и не вызывает аномалий в Performance API.
// Внедряем микропейлоад до парсинга HTML await page.evaluateOnNewDocument(() => { // Снимаем только самую главную метку бота Object.defineProperty(navigator, 'webdriver', { get: () => undefined, }); // Никаких тяжелых подмен WebGL или Canvas здесь нет! });
// Внедряем микропейлоад до парсинга HTML await page.evaluateOnNewDocument(() => { // Снимаем только самую главную метку бота Object.defineProperty(navigator, 'webdriver', { get: () => undefined, }); // Никаких тяжелых подмен WebGL или Canvas здесь нет! });
Всю остальную логику (подмену отпечатков видеокарты, аудио, плагинов) грузите позже, когда браузер уже начал отрисовывать страницу. Это можно сделать через стандартный page.evaluate или привязав к событию DOMContentLoaded.
// Идем на сайт await page.goto('https://target-site.com'); // Страница уже грузится, тайминги антифрода пройдены. // Теперь можно безопасно внедрять тяжелую маскировку await page.evaluate(() => { // Здесь подменяем Canvas, WebGL, шрифты и прочее const getParameter = WebGLRenderingContext.getParameter; WebGLRenderingContext.prototype.getParameter = function(parameter) { if (parameter === 37445) return 'Intel Inc.'; if (parameter === 37446) return 'Intel Iris OpenGL Engine'; return getParameter(parameter); }; });
// Идем на сайт await page.goto('https://target-site.com'); // Страница уже грузится, тайминги антифрода пройдены. // Теперь можно безопасно внедрять тяжелую маскировку await page.evaluate(() => { // Здесь подменяем Canvas, WebGL, шрифты и прочее const getParameter = WebGLRenderingContext.getParameter; WebGLRenderingContext.prototype.getParameter = function(parameter) { if (parameter === 37445) return 'Intel Inc.'; if (parameter === 37446) return 'Intel Iris OpenGL Engine'; return getParameter(parameter); }; });
Системы защиты, как правило, проверяют webdriver синхронно в самом начале — и такой способ успешно проходит подобную проверку. А более глубокие тесты антифрод запускает асинхронно, уже в процессе работы со страницей. К этому моменту второй, более тяжелый скрипт уже успеет загрузиться и подменить нужные функции, не создав задержек в начале жизненного цикла страницы.
Проблема Network.setUserAgentOverride
Сменить User-Agent через функционал CDP легко, но проблема в том, что так меняется только HTTP-заголовок, а другие маркеры среды остаются нетронутыми.
А значит, вы получаете критический рассинхрон. Метод Network.setUserAgentOverride не способен корректно подменить внутренние свойства объекта navigator или специфические заголовки Client Hints. Аналитическая система видит, к примеру, мобильный User-Agent в заголовке, но фиксирует десктопное поведение API-рендеринга и несовпадающие заголовки sec-ch-ua.
Никогда не меняйте только строчку User-Agent. Если вы беретесь за эмуляцию, вам нужно подделывать весь комплекс Client Hints и аппаратных сенсоров. Вместо setUserAgent используйте расширенные параметры CDP, передавая объект userAgentMetadata, и не забывайте о специфичных паттернах, характерных для мобильных устройств (как минимум эмуляция тачскрина).
Сканирование TCP-портов отладки
Чтобы Puppeteer мог управлять браузером, он запускает Chromium с открытым портом для WebSocket-соединения. По умолчанию это часто локальный порт отладки (классический 9222 или любой другой случайный порт).
Антифрод-скрипт загружается прямо в браузер пользователя. И уже от имени вашего браузера, находясь внутри вашей системы, он делает банальный AJAX-запрос по адресу http://127.0.0.1:9222/json/version.
Защита использует так называемые тайминг-атаки или сканирование локальной сети через WebSocket. Если скрипт стучится в стандартный порт отладки и получает мгновенный ответ от движка — он понимает, что браузером сейчас управляет скрипт.
Можно пытаться менять порты на случайные, но сканеры умеют простукивать целые диапазоны. Самое простое решение — вообще отказаться от использования TCP-портов для связи между Node.js и браузером.
Puppeteer умеет общаться с Chromium не через сетевые сокеты, а через анонимные каналы связи операционной системы. В этом режиме порты отладки вообще не открываются, и сканировать антифроду нечего.
Просто добавьте один флаг pipe: true при запуске вашего браузера. Это защитит вас от сетевого сканирования локального хоста.
Как добиться результата без детекта
Абсолютная невидимость Puppeteer невозможна в принципе, так как любая эмуляция оставляет отклонения. С технической точки зрения так и есть, в чем мы уже убедились выше.
Базовых плагинов по умолчанию недостаточно для серьезных задач. Если вам нужна высокая анонимность, придется менять подход:
Патчинг бинарного файла Chrome. Это физическая модификация исполняемого файла браузера (например, в hex-редакторе). Цель — заменить жестко закодированные строки протокола на случайные символы в самом исполняемом файле. В отличие от Stealth-плагинов, которые оставляют микроскопическое окно уязвимости между запуском и внедрением JS, бинарная модификация полностью убирает проблему.
Использование специализированных движков. Переход на решения архитектурного уровня (антидетект-браузер), где подмена отпечатков и скрытие автоматизации происходят на уровне кода C++ движка Blink, а не через JS-инъекции.
Отказ от CDP. Перенос логики управления в кастомные расширения Chrome, которые общаются с сервером управления по своим WebSocket-каналам, позволяя закрыть порты отладки.
Использование антидетект-браузеров
Отдельно стоит отметить использование специализированных инструментов, например антидетект-браузеров. Они подходят к задаче через внесение изменений в исходный код Chromium и работу напрямую в ядре. Естественно, очевидные маркеры вроде navigator.webdriver просто физически вырезаются еще на этапе компиляции бинарного файла.
Вся тяжелая работа по эмуляции среды происходит под капотом. Когда скрипт защиты запрашивает параметры видеокарты (WebGL vendor/renderer), движку не нужно запускать JS-обертку для подмены — код C++ нативно и моментально отдает нужные значения. То же самое касается сложных метрик. А пользователь и вовсе не касается всего вышеперечисленного, разве что на уровне настройки нужных параметров перед запуском профиля.
С точки зрения антифрода, такой браузер выглядит как обычный пользователь. А вы можете управлять отпечатком через Puppeteer, просто подключив его к открытому порту антидетекта.
Неочевидные детали и вывод
Использование флага
--disable-blink-features=AutomationControlledснимает базовую меткуwebdriver, но оставляет косвенные следы.Принудительное отключение
Log.enableиPerformance.enableчерез протокол лишает ваш скрипт части метрик, но повышает общую скрытность.Новый Headless Mode (
--headless=new) объединяет архитектуры обычного и headless-браузера, что меняет структуру детектированияClientRectsи шрифтов, делая рендеринг более естественным.
Успешный обход защиты начинается не с установки сотни npm-пакетов, а с понимания того, как работает движок под капотом.
Работа с автоматизацией сегодня требует инженерной педантичности. CDP-утечки — это не баг, а конструктивная особенность инструмента. Изучайте браузер глубже, контролируйте каждый исполняемый контекст, и тогда ваши системы станут по-настоящему устойчивыми.
Сохраняйте анонимность с Octo Browser, ведь отследить ваш реальный цифровой отпечаток невозможно.
Что такое CDP и почему он оставляет цифровые следы
Chrome DevTools Protocol предоставляет низкоуровневый доступ к архитектуре браузера. Изначально он создавался для отладки, инспектирования и профилирования, но не для скрытного парсинга.
Протокол функционирует через WebSocket-соединения, напрямую взаимодействуя с движком V8. Когда ваш скрипт отправляет команду (например, клик или переход по ссылке), браузер открывает локальный сокет для двустороннего обмена данными. Этот процесс неизбежно формирует внутренние логи, меняет распределение памяти и оставляет артефакты в изолированной среде браузера. Защитные механизмы способны анализировать эти микроизменения и тайминги для выявления машинного поведения. Следовательно, сам факт поддержания соединения уже демаскирует вашу автоматизацию.
В чем отличие CDP-утечек от стандартного фингерпринтинга
Важно понимать, что CDP-утечки и утечки через отпечатки браузера — это два принципиально разных вектора обнаружения.
Классический отпечаток (Canvas, WebGL, Fonts): идентифицирует уникальное оборудование и особенности рендеринга видеокарты. Несовершенство отпечатка или рассинхрон данных позволяют антибот-системам выявлять подмену и маркировать пользователя как бота.
CDP-утечки: раскрывают поведенческие и структурные маркеры исполнения кода. Они показывают сам факт удаленного управления браузером.
У вас может быть уникальный отпечаток WebGL, но флаги автоматизации сведут эту уникальность к нулю. Если проверка отпечатка браузера требует ресурсозатратных операций и времени, то проверка переменных среды или стека вызовов не стоит практически ничего. Именно поэтому антифрод-системам выгодно использовать CDP-детекты. Они позволяют отсекать ботов на этапе базовых протокольных проверок, экономя серверные мощности на глубоком поведенческом анализе.
Анатомия утечек через Chrome DevTools Protocol
Маркеры контекста исполнения (Execution Context)
Главная слабость Puppeteer в том, как именно он заставляет браузер выполнять ваш код. Под капотом метод page.evaluate() использует CDP-команду Runtime.evaluate.
При передаче скрипта в браузер современные версии Puppeteer формируют специфический Source URL, который привязывается к этому куску кода. Если в процессе выполнения скрипта возникает ошибка, движок V8 формирует стандартный стек вызовов, и там появляется примерно следующее:
at pptr:evaluate;C:\Users\Admin\Projects\...\main.js:17:14
Антифрод, переопределяя базовые функции, проактивно провоцирует невидимые ошибки и читает объект Error.stack. Он видит там:
Префикс
pptr:— прямую аббревиатуру Puppeteer.Абсолютный путь к файлу на вашем локальном диске или сервере (начинающийся с
C:\или/var/www/...).
Браузер реального пользователя, находясь на публичном сайте, никогда не исполняет код с локальных путей файловой системы, соответственно, подобные флаги — это стопроцентный маркер автоматизации.

Эту проблему не решает смена IP-адреса или подмена Canvas. Чтобы скрыть этот маркер, вам придется вмешаться либо в исходники библиотеки, либо в среду исполнения браузера.
Самый надежный способ — вырезать маркер pptr:evaluate из исходного кода библиотеки Puppeteer до того, как она отправит команду в браузер. Искать и править файлы руками после каждого npm install нерационально, поэтому лучше написать простой патчер:
const fs = require('fs'); const path = require('path'); // Путь к файлу ExecutionContext.js в новых версиях Puppeteer const targetFile = path.resolve(__dirname, 'node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/ExecutionContext.js'); if (fs.existsSync(targetFile)) { let content = fs.readFileSync(targetFile, 'utf8'); // Заменяем префикс pptr:evaluate на анонимный вызов // и обрезаем локальный путь к файлу const patchedContent = content.replace(/pptr:evaluate;.*?\\n/g, 'anonymous:evaluation;\n'); fs.writeFileSync(targetFile, patchedContent, 'utf8'); console.log('Puppeteer пропатчен! Маркеры pptr:evaluate удалены.'); }
Если нет возможности исправить node_modules, маркеры можно вырезать на лету, внедрив маскировочный скрипт до загрузки страницы сайта. Он переопределяет базовое поведение объекта Error и фильтрует стек вызовов:
await page.evaluateOnNewDocument(() => { // Сохраняем оригинальный класс Error const NativeError = window.Error; window.Error = function(...args) { const err = new NativeError(...args); const originalStack = err.stack; if (originalStack) { Object.defineProperty(err, 'stack', { get: function() { // Разбиваем стек на строки и вырезаем маркер Puppeteer return originalStack .split('\n') .filter(line => !line.includes('pptr:evaluate')) .join('\n'); } }); } return err; }; // Восстанавливаем цепочку прототипов, чтобы она не выдала нас на проверке window.Error.prototype = NativeError.prototype; });

В итоге вместо локальных путей будет выводиться примерно такой текст
Уязвимости метода Page.addScriptToEvaluateOnNewDocument
Попытки скрыть автоматизацию (включая использование популярных stealth-плагинов) чаще всего сводятся к инъекции маскировочного JavaScript до загрузки страницы целевого сайта. В Puppeteer это делается через команду Page.addScriptToEvaluateOnNewDocument.
Сам факт использования этого метода оставляет четкие следы в среде исполнения.
Аномалии таймингов: команда заставляет движок V8 синхронно выполнить ваш код в момент создания контекста страницы, до того как HTML-парсер начнет свою работу. Внедрение объемных скриптов создает ощутимые микрозадержки на этапе
document_start. Антифрод измеряет время между внутренними событиями браузера и видит неестественный временной провал.Нарушение жизненного цикла: stealth-плагины не могут просто стереть флаги автоматизации. Они используют сложные перехватчики. Защитный механизм выявляет, что сложные прокси-объекты и переопределенные свойства появились в объекте
windowаномально рано — до событияDOMContentLoadedили даже до парсинга тега<head>. Это нарушает естественный жизненный цикл страницы.Отсутствие изоляции: любые скрипты, внедренные через CDP-команду
addScriptToEvaluateOnNewDocument, выполняются в главном контексте страницы. Это значит, что код вашего маскировочного плагина и скрипты антифрод-системы сайта оказываются в одном пространстве. Малейший баг или случайно оставленная переменная — и защита сайта их увидит.
Отказаться от маскировки нельзя, но можно изменить способ доставки маскировочного кода. Вместо того чтобы вбрасывать код через протокол отладки, можно использовать нативный механизм самого браузера — загрузку собственного расширения.
Браузеры проектировались так, чтобы расширения могли безопасно внедрять код. Если упаковать логику маскировки в расширение Manifest V3 и загрузить его при старте Puppeteer, антифрод воспримет эти тайминги и инъекции как работу обычного пользовательского аддона.
Например, вы можете создать папку stealth-extension с файлом manifest.json:
{ "manifest_version": 3, "name": "My Custom Stealth", "version": "1.0", "content_scripts": [ { "matches": ["<all_urls>"], "js": ["inject.js"], "run_at": "document_start", "world": "MAIN" } ] }
И подключить его при запуске Puppeteer вместо вызова evaluateOnNewDocument:
const browser = await puppeteer.launch({ args: [ `--disable-extensions-except=${pathToExtension}`, `--load-extension=${pathToExtension}` ] });
Второй способ — вообще не использовать механизмы V8 для инъекции. Вы можете использовать внешний прокси-сервер или встроенный интерцептор запросов Puppeteer, чтобы модифицировать сырой HTML-ответ от сервера на лету.
Вставьте свой тег <script> с маскировкой в самую первую строку тега <head>. В этом случае код выполнится абсолютно естественным образом в рамках стандартного парсинга документа, не вызывая подозрений у счетчиков таймингов.
Если нужно обязательно использовать addScriptToEvaluateOnNewDocument, откажитесь от загрузки монолитных stealth-плагинов. Разделите маскировку на два этапа.
Перед загрузкой страницы избавьтесь только от маркера webdriver. Этот код выполняется за доли миллисекунды и не вызывает аномалий в Performance API.
// Внедряем микропейлоад до парсинга HTML await page.evaluateOnNewDocument(() => { // Снимаем только самую главную метку бота Object.defineProperty(navigator, 'webdriver', { get: () => undefined, }); // Никаких тяжелых подмен WebGL или Canvas здесь нет! });
Всю остальную логику (подмену отпечатков видеокарты, аудио, плагинов) грузите позже, когда браузер уже начал отрисовывать страницу. Это можно сделать через стандартный page.evaluate или привязав к событию DOMContentLoaded.
// Идем на сайт await page.goto('https://target-site.com'); // Страница уже грузится, тайминги антифрода пройдены. // Теперь можно безопасно внедрять тяжелую маскировку await page.evaluate(() => { // Здесь подменяем Canvas, WebGL, шрифты и прочее const getParameter = WebGLRenderingContext.getParameter; WebGLRenderingContext.prototype.getParameter = function(parameter) { if (parameter === 37445) return 'Intel Inc.'; if (parameter === 37446) return 'Intel Iris OpenGL Engine'; return getParameter(parameter); }; });
Системы защиты, как правило, проверяют webdriver синхронно в самом начале — и такой способ успешно проходит подобную проверку. А более глубокие тесты антифрод запускает асинхронно, уже в процессе работы со страницей. К этому моменту второй, более тяжелый скрипт уже успеет загрузиться и подменить нужные функции, не создав задержек в начале жизненного цикла страницы.
Проблема Network.setUserAgentOverride
Сменить User-Agent через функционал CDP легко, но проблема в том, что так меняется только HTTP-заголовок, а другие маркеры среды остаются нетронутыми.
А значит, вы получаете критический рассинхрон. Метод Network.setUserAgentOverride не способен корректно подменить внутренние свойства объекта navigator или специфические заголовки Client Hints. Аналитическая система видит, к примеру, мобильный User-Agent в заголовке, но фиксирует десктопное поведение API-рендеринга и несовпадающие заголовки sec-ch-ua.
Никогда не меняйте только строчку User-Agent. Если вы беретесь за эмуляцию, вам нужно подделывать весь комплекс Client Hints и аппаратных сенсоров. Вместо setUserAgent используйте расширенные параметры CDP, передавая объект userAgentMetadata, и не забывайте о специфичных паттернах, характерных для мобильных устройств (как минимум эмуляция тачскрина).
Сканирование TCP-портов отладки
Чтобы Puppeteer мог управлять браузером, он запускает Chromium с открытым портом для WebSocket-соединения. По умолчанию это часто локальный порт отладки (классический 9222 или любой другой случайный порт).
Антифрод-скрипт загружается прямо в браузер пользователя. И уже от имени вашего браузера, находясь внутри вашей системы, он делает банальный AJAX-запрос по адресу http://127.0.0.1:9222/json/version.
Защита использует так называемые тайминг-атаки или сканирование локальной сети через WebSocket. Если скрипт стучится в стандартный порт отладки и получает мгновенный ответ от движка — он понимает, что браузером сейчас управляет скрипт.
Можно пытаться менять порты на случайные, но сканеры умеют простукивать целые диапазоны. Самое простое решение — вообще отказаться от использования TCP-портов для связи между Node.js и браузером.
Puppeteer умеет общаться с Chromium не через сетевые сокеты, а через анонимные каналы связи операционной системы. В этом режиме порты отладки вообще не открываются, и сканировать антифроду нечего.
Просто добавьте один флаг pipe: true при запуске вашего браузера. Это защитит вас от сетевого сканирования локального хоста.
Как добиться результата без детекта
Абсолютная невидимость Puppeteer невозможна в принципе, так как любая эмуляция оставляет отклонения. С технической точки зрения так и есть, в чем мы уже убедились выше.
Базовых плагинов по умолчанию недостаточно для серьезных задач. Если вам нужна высокая анонимность, придется менять подход:
Патчинг бинарного файла Chrome. Это физическая модификация исполняемого файла браузера (например, в hex-редакторе). Цель — заменить жестко закодированные строки протокола на случайные символы в самом исполняемом файле. В отличие от Stealth-плагинов, которые оставляют микроскопическое окно уязвимости между запуском и внедрением JS, бинарная модификация полностью убирает проблему.
Использование специализированных движков. Переход на решения архитектурного уровня (антидетект-браузер), где подмена отпечатков и скрытие автоматизации происходят на уровне кода C++ движка Blink, а не через JS-инъекции.
Отказ от CDP. Перенос логики управления в кастомные расширения Chrome, которые общаются с сервером управления по своим WebSocket-каналам, позволяя закрыть порты отладки.
Использование антидетект-браузеров
Отдельно стоит отметить использование специализированных инструментов, например антидетект-браузеров. Они подходят к задаче через внесение изменений в исходный код Chromium и работу напрямую в ядре. Естественно, очевидные маркеры вроде navigator.webdriver просто физически вырезаются еще на этапе компиляции бинарного файла.
Вся тяжелая работа по эмуляции среды происходит под капотом. Когда скрипт защиты запрашивает параметры видеокарты (WebGL vendor/renderer), движку не нужно запускать JS-обертку для подмены — код C++ нативно и моментально отдает нужные значения. То же самое касается сложных метрик. А пользователь и вовсе не касается всего вышеперечисленного, разве что на уровне настройки нужных параметров перед запуском профиля.
С точки зрения антифрода, такой браузер выглядит как обычный пользователь. А вы можете управлять отпечатком через Puppeteer, просто подключив его к открытому порту антидетекта.
Неочевидные детали и вывод
Использование флага
--disable-blink-features=AutomationControlledснимает базовую меткуwebdriver, но оставляет косвенные следы.Принудительное отключение
Log.enableиPerformance.enableчерез протокол лишает ваш скрипт части метрик, но повышает общую скрытность.Новый Headless Mode (
--headless=new) объединяет архитектуры обычного и headless-браузера, что меняет структуру детектированияClientRectsи шрифтов, делая рендеринг более естественным.
Успешный обход защиты начинается не с установки сотни npm-пакетов, а с понимания того, как работает движок под капотом.
Работа с автоматизацией сегодня требует инженерной педантичности. CDP-утечки — это не баг, а конструктивная особенность инструмента. Изучайте браузер глубже, контролируйте каждый исполняемый контекст, и тогда ваши системы станут по-настоящему устойчивыми.
Следите за последними новостями Octo Browser
Нажимая кнопку, вы соглашаетесь с нашей политикой конфиденциальности.
Следите за последними новостями Octo Browser
Нажимая кнопку, вы соглашаетесь с нашей политикой конфиденциальности.
Следите за последними новостями Octo Browser
Нажимая кнопку, вы соглашаетесь с нашей политикой конфиденциальности.

Присоединяйтесь к Octo Browser сейчас
Вы можете обращаться за помощью к нашим специалистам службы поддержки в чате в любое время.

Присоединяйтесь к Octo Browser сейчас
Вы можете обращаться за помощью к нашим специалистам службы поддержки в чате в любое время.
Присоединяйтесь к Octo Browser сейчас
Вы можете обращаться за помощью к нашим специалистам службы поддержки в чате в любое время.