Маскування 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-операції залишають сліди в метриках продуктивності браузера:

const entries = performance.getEntriesByType('resource');
const cdpRequests = entries.filter(e => 
    e.name.includes('devtools') || 
    e.name.includes('inspector')
);

if (performance.memory) {
    const ratio = performance.memory.usedJSHeapSize / 
                  performance.memory.totalJSHeapSize;
    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% довічну комісію з кожного реферала.

Стати партнером →