Маскировка CDP: Почему Playwright и Puppeteer детектируются за секунды

· 15 мин чтения
cdp playwright puppeteer detection automation
Маскировка CDP: Почему Playwright и Puppeteer детектируются за секунды

Готовы защитить свою цифровую личность?

Выберите тариф и запускайте незаметные профили уже сегодня.

Начать

Chrome DevTools Protocol (CDP) — стандартный интерфейс для автоматизации браузеров. Playwright, Puppeteer и большинство Selenium-стеков используют его для управления Chromium-инстансами. Проблема проста: каждое CDP-подключение оставляет следы, которые антифрод-системы обнаруживают в течение миллисекунд после загрузки страницы. Никакое количество стелс-плагинов, аргументов запуска или runtime-патчей не устраняет эти следы в Chromium полностью — и индустрия детекции точно знает, где искать.

В этой статье мы каталогизируем полный набор векторов детекции CDP по состоянию на 2026 год, объясним, почему поверхностные патчи не работают, и опишем, что на самом деле представляет собой модификация браузера на уровне ядра.

Поверхность детекции: полная инвентаризация

Детекция CDP — это не единичная проверка. Это многоуровневая система из десятков сигналов, каждый из которых по отдельности слаб, но вместе — разоблачителен. Мы можем разделить их на пять классов.

Класс 1: Флаг navigator.webdriver

Самый известный вектор детекции. Когда Chromium запускается с включённой автоматизацией, navigator.webdriver возвращает true. Спецификация WebDriver (W3C) предписывает это поведение.

// Проверка детекции
if (navigator.webdriver === true) {
    reportBot();
}

Типичный «фикс»: Переопределение через CDP:

await page.evaluateOnNewDocument(() => {
    Object.defineProperty(navigator, 'webdriver', {
        get: () => false,
    });
});

Почему не работает: Это переопределение выполняется в JavaScript-контексте страницы, но антидетект-скрипты могут проверить само переопределение:

// Детекция переопределения
const descriptor = Object.getOwnPropertyDescriptor(navigator, 'webdriver');
if (descriptor && descriptor.get && descriptor.get.toString().includes('false')) {
    reportBot(); // Свойство было переопределено геттером
}

// Проверка цепочки прототипов
if (navigator.webdriver !== Navigator.prototype.webdriver) {
    reportBot(); // Несовпадение между инстансом и прототипом
}

// Изоляция через iframe
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
if (iframe.contentWindow.navigator.webdriver === true) {
    reportBot(); // Переопределение не распространилось на новый контекст
}

Техника с iframe особенно разрушительна: каждый новый browsing context получает свежий объект navigator. Если ваш патч затрагивает только основной фрейм, любой динамически созданный iframe раскроет реальное значение.

Класс 2: CDP-специфичные runtime-объекты

Когда CDP-сессия активна, Chromium экспонирует внутренние объекты, которых нет в обычных экземплярах браузера:

// Runtime.enable создаёт это
window.__cdp_runtime_enabled // внутренний флаг

// Артефакты Page.addScriptToEvaluateOnNewDocument
window.__playwright_evaluation_script_

// Puppeteer-специфичные
window.__puppeteer_utility_world__

Современные скрипты детекции перечисляют все свойства window и сравнивают их с известным чистым профилем браузера:

function detectCDPArtifacts() {
    const knownClean = new Set([/* сотни стандартных свойств */]);
    const current = Object.getOwnPropertyNames(window);
    
    for (const prop of current) {
        if (!knownClean.has(prop)) {
            // Неизвестное свойство — потенциальный артефакт автоматизации
            logSuspicious(prop);
        }
    }
    
    // Проверка протоколов отладки
    if (window.cdc_adoQpoasnfa76pfcZLmcfl_Array ||
        window.cdc_adoQpoasnfa76pfcZLmcfl_Promise ||
        window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol) {
        reportBot(); // Управляющие переменные ChromeDriver
    }
}

Переменные с префиксом cdc_ — это управляющие переменные ChromeDriver, инжектируемые в каждую страницу. Их имена частично рандомизируются для каждой сборки ChromeDriver, но паттерн (cdc_ + случайная строка + _ + имя JS-типа) остаётся стабильным.

Класс 3: Аргументы запуска Chrome

Инструменты автоматизации передают Chromium специфические аргументы командной строки. Эти аргументы оставляют следы, которые JavaScript может обнаружить:

# Типичные аргументы запуска Puppeteer
--disable-blink-features=AutomationControlled
--enable-automation
--remote-debugging-port=9222
--disable-background-networking
--disable-default-apps
--disable-extensions
--disable-sync
--disable-translate
--metrics-recording-only
--no-first-run

Детекция через внутренние API Chrome:

// chrome.runtime модифицируется при --enable-automation
if (chrome.runtime && chrome.runtime.id === undefined && 
    chrome.runtime.connect === undefined) {
    reportBot(); // Режим автоматизации урезает возможности runtime
}

// Проверка поведения Permissions API
if (!navigator.permissions || 
    navigator.permissions.query === undefined) {
    reportBot();
}

// Разрешение уведомлений в режиме автоматизации
navigator.permissions.query({ name: 'notifications' }).then(result => {
    if (result.state === 'denied' && !wasExplicitlyDenied()) {
        reportBot(); // Режим автоматизации автоматически запрещает уведомления
    }
});

Класс 4: Детекция headless-режима

Даже с --headless=new (новый headless в Chrome 112+) различия сохраняются:

// Различия строк WebGL-рендерера
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
const renderer = gl.getParameter(gl.RENDERER);
const vendor = gl.getParameter(gl.VENDOR);

// Headless Chrome часто возвращает:
// RENDERER: "SwiftShader" или "Google SwiftShader"
// VENDOR: "Google Inc. (Google)"
// Реальный Chrome возвращает информацию о GPU:
// RENDERER: "ANGLE (NVIDIA, NVIDIA GeForce RTX 3080 Direct3D11 ...)"
// VENDOR: "Google Inc. (NVIDIA)"

// Количество плагинов
if (navigator.plugins.length === 0) {
    reportBot(); // В headless нет плагинов
}

// Консистентность языков
if (navigator.languages.length === 0 || 
    navigator.languages[0] !== navigator.language) {
    reportBot();
}

// Размеры экрана в headless
if (window.outerWidth === 0 || window.outerHeight === 0) {
    reportBot();
}

Класс 5: Поведенческий анализ

Помимо статических проверок свойств, антифрод-системы анализируют паттерны взаимодействия, характерные для CDP-автоматизации:

// Паттерны движения мыши
// CDP Input.dispatchMouseEvent создаёт синтетические события
// без промежуточных move-событий, которые генерирует реальная мышь
document.addEventListener('mousemove', (e) => {
    movements.push({
        x: e.clientX, y: e.clientY,
        timestamp: e.timeStamp,
        isTrusted: e.isTrusted
    });
});

// После сбора событий:
function analyzeMovements(events) {
    // Реальные люди создают кривые с ускорением
    // CDP создаёт прямые линии или без промежуточных точек
    for (let i = 1; i < events.length; i++) {
        const dt = events[i].timestamp - events[i-1].timestamp;
        const dx = events[i].x - events[i-1].x;
        const dy = events[i].y - events[i-1].y;
        const distance = Math.sqrt(dx*dx + dy*dy);
        
        // Телепортирующаяся мышь (большое расстояние, малое время)
        if (distance > 100 && dt < 5) {
            scores.teleport++;
        }
        
        // Идеально прямые линии (нулевая кривизна)
        if (i >= 2) {
            const curvature = computeCurvature(
                events[i-2], events[i-1], events[i]
            );
            if (curvature === 0) scores.straight++;
        }
    }
}

Почему стелс-плагины недостаточны

Пакет puppeteer-extra-plugin-stealth и аналогичные решения пытаются патчить известные векторы детекции в runtime. Вот почему этот подход архитектурно порочен.

Проблема тайминга

Стелс-плагины инжектируют патчи через Page.addScriptToEvaluateOnNewDocument. Эта CDP-команда выполняет JavaScript до скриптов страницы, но после инициализации внутреннего состояния браузера. Антидетект-проверки, запускающиеся в привилегированном контексте браузера (Trusted Types, обработчики нарушений CSP), могут наблюдать оригинальное состояние до применения патчей.

Проблема полноты

Каждое обновление Chromium вводит новые поверхности детекции. Сообщество стелс-плагинов вечно реактивно — патчит векторы после их появления в антифрод-SDK. Индустрия детекции (DataDome, PerimeterX/HUMAN, Kasada, Akamai Bot Manager) содержит штатных исследователей, обнаруживающих новые векторы, в то время как open-source стелс-сообщество латает их неделями или месяцами позже.

Проблема консистентности

Патчинг отдельных свойств создаёт несогласованности. Если вы переопределяете navigator.webdriver, но не патчите соответствующий Blink internal, который его питает, кросс-проверка двух значений выявляет ложь:

// Внутренняя проверка V8
const workerBlob = new Blob([`
    postMessage(navigator.webdriver);
`], { type: 'application/javascript' });
const worker = new Worker(URL.createObjectURL(workerBlob));
worker.onmessage = (e) => {
    if (e.data !== navigator.webdriver) {
        reportBot(); // В worker другое значение webdriver
    }
};

Worker’ы выполняются в отдельном контексте. Если ваш патч модифицирует только контекст главного окна, worker’ы раскроют истинное значение.

Глубокий патчинг ядра: единственный жизнеспособный подход

Термин «патчинг ядра» в контексте антидетекта означает модификацию исходного кода браузерного движка на уровне компиляции, а не модификации ядра операционной системы. Именно этот подход используют браузеры вроде Camoufox (на котором основан Santiago) и аналогичные проекты.

Что именно патчится

1. Флаг WebDriver на уровне движка

Вместо переопределения navigator.webdriver через JavaScript, флаг удаляется из IDL-определения браузера:

// В Chromium: third_party/blink/renderer/core/frame/navigator.idl
// ДО:
[RuntimeEnabled=AutomationControlled] readonly attribute boolean webdriver;

// ПОСЛЕ (патч):
// Строка удалена полностью — свойство не существует вообще

В Firefox/Gecko (подход Camoufox):

// dom/webidl/Navigator.webidl
// Атрибут webdriver выкомпилирован из сборки
// Runtime-переопределение не нужно — свойства действительно нет

2. Удаление артефактов CDP

Управляющие переменные вроде cdc_* удаляются из исходного кода драйвера:

// До: ChromeDriver инжектирует управляющие переменные
ExecuteScript("window.cdc_" + randomSuffix + "_Array = Array;");

// После: Код инъекции удалён полностью
// Нечего детектировать, потому что переменные никогда не создаются

3. Флаги автоматизации в бинарнике

Флаг --enable-automation Chromium запускает многочисленные внутренние механизмы. В патченой сборке:

// content/browser/renderer_host/render_process_host_impl.cc
// Фича AutomationControlled отключена на этапе компиляции
// Runtime-проверка этого флага безусловно возвращает false

4. Поверхности детекции HeadlessMode

Патченые браузеры гарантируют, что headless- и headed-режимы создают идентичные отпечатки:

// SwiftShader заменяется реальными данными эмуляции GPU
// Список плагинов заполняется одинаково в обоих режимах
// Метрики экрана возвращают реалистичные значения

Преимущества Firefox/Gecko

Chromium — наиболее изученный браузер для детекции автоматизации; вендоры антифрод инвестируют значительные ресурсы в Chromium-специфичную детекцию. Firefox получает меньше внимания, что является одной из причин, почему Camoufox (и, соответственно, Santiago) выбрал Gecko:

  1. Меньшая поверхность детекции: В базах антифрод-вендоров меньше известных артефактов автоматизации Gecko
  2. Другая архитектура: Многопроцессная модель Gecko отличается от модели Chromium, поэтому Chromium-специфичные скрипты детекции дают false positive на Firefox
  3. Marionette vs CDP: Нативный протокол автоматизации Firefox (Marionette) оставляет другие следы, чем CDP, а WebDriver BiDi (преемник) ещё чище
  4. Меньше исследователей: Экономика антифрод-исследований благоприятствует анализу Chromium, потому что большинство трафика автоматизации использует Chromium

Детекция за пределами браузера

Даже при идеальной маскировке на уровне браузера, CDP-автоматизация может быть обнаружена через инфраструктурные сигналы:

Паттерны подключений

Реальный пользователь: DNS → TCP → TLS → HTTP (последовательно, с естественными задержками)
CDP-бот:               Переиспользует пул соединений, параллельные запросы, нет простоя

Антифрод-системы мониторят поведение на уровне соединений: как быстро запросы следуют за загрузкой страницы, следуют ли запросы подресурсов естественному порядку зависимостей, есть ли в соединении периоды простоя, согласующиеся с временем чтения.

WebSocket порт отладки

CDP коммуницирует через WebSocket. Даже если порт отладки не экспонирован наружу, сканирование локальных портов из JavaScript может его обнаружить:

// Сканирование портов через тайминг WebSocket
async function checkPort(port) {
    const start = performance.now();
    try {
        const ws = new WebSocket(`ws://127.0.0.1:${port}`);
        ws.onerror = () => {};
        await new Promise(r => setTimeout(r, 100));
        ws.close();
    } catch (e) {}
    const elapsed = performance.now() - start;
    // Открытый порт: быстрое подключение или быстрая ошибка
    // Закрытый порт: более медленный таймаут
    return elapsed < 50;
}

// Проверка типичных портов отладки
for (const port of [9222, 9229, 9333, 0]) {
    if (await checkPort(port)) {
        reportBot();
    }
}

Сигнатуры памяти и производительности

CDP-операции оставляют следы в метриках производительности браузера:

// Автоматизация раздувает определённые performance entries
const entries = performance.getEntriesByType('resource');
const cdpRequests = entries.filter(e => 
    e.name.includes('devtools') || 
    e.name.includes('inspector')
);

// Давление на память от overhead CDP
if (performance.memory) {
    const ratio = performance.memory.usedJSHeapSize / 
                  performance.memory.totalJSHeapSize;
    // CDP-сессии увеличивают базовое потребление памяти
    if (ratio > 0.9 && performance.memory.jsHeapSizeLimit < 2e9) {
        logSuspicious('memory_pressure');
    }
}

Подход Santiago: стелс на уровне протокола

Архитектура Santiago полностью избегает проблемы детекции CDP благодаря выбору браузерного движка и протокола автоматизации:

1. Движок Gecko: Используя Firefox (через Camoufox), Santiago работает вне основной экосистемы детекции CDP. Подавляющее большинство антифрод-скриптов оптимизировано для Chromium.

2. Модификации на уровне исходного кода: Все индикаторы автоматизации удалены на уровне исходного кода до компиляции. Runtime-патчинг отсутствует — артефакты автоматизации просто не существуют в бинарнике.

3. Нативная изоляция профилей: Каждый браузерный профиль работает как независимый инстанс со своим состоянием, cookies, local storage и отпечатком. Нет общей CDP-сессии, мультиплексирующей несколько профилей.

4. Нет экспонированного интерфейса отладки: Интерфейс автоматизации — не стандартный протокол отладки, а внутренний IPC-механизм, не создающий видимых сетевых эндпоинтов.

Практическая верификация: тестирование вашей конфигурации

Если вам нужно проверить, детектируема ли ваша текущая автоматизация, используйте эти ресурсы:

Онлайн-тесты детекции

  • bot.sannysoft.com — комплексный тест детекции автоматизации
  • browserleaks.com — полный анализ отпечатков, включая артефакты CDP
  • abrahamjuliot.github.io/creepjs — CreepJS, один из наиболее агрессивных инструментов фингерпринтинга

Скрипт ручной верификации

// Запустите это в консоли вашего автоматизированного браузера
const checks = {
    webdriver: navigator.webdriver,
    webdriverProto: Navigator.prototype.webdriver,
    plugins: navigator.plugins.length,
    languages: navigator.languages.length,
    chrome: !!window.chrome,
    permissions: !!navigator.permissions,
    webgl: (() => {
        const c = document.createElement('canvas');
        const gl = c.getContext('webgl');
        return gl ? gl.getParameter(gl.RENDERER) : 'unavailable';
    })(),
    cdcVars: Object.getOwnPropertyNames(window)
        .filter(p => p.startsWith('cdc_')),
    domAutomation: !!window.domAutomation,
    domAutomationController: !!window.domAutomationController,
};

console.table(checks);

Заключение

Детекция CDP-автоматизации — это не игра в кошки-мышки с равными шансами. У стороны детекции структурные преимущества: они контролируют среду выполнения JavaScript, могут обновлять скрипты детекции серверно за секунды и извлекают выгоду из коллективного интеллекта тысяч сайтов, использующих один и тот же антифрод-SDK.

Единственная устойчивая защита — не быть детектируемым в принципе. Это означает либо использование браузерного движка с удалёнными на уровне исходного кода следами автоматизации, либо принятие факта, что ваш CDP-стек на Playwright/Puppeteer будет отмечен на любом сайте с современной антифрод-защитой. Промежуточного варианта нет — стелс-плагины это временная заплатка поверх перманентного архитектурного дефекта.

Готовы защитить свою цифровую личность?

Выберите тариф и запускайте незаметные профили уже сегодня.

Получайте 15% пожизненную комиссию с каждого реферала.

Стать партнёром →