cdpfleet Docs GitHub Dashboard

Cases / #5 · 2026-09-30 · Hard

Four Chromium builds against common bot checks, headless and headful

chromium, Chrome, Patchright and CloakBrowser through the checks detection scripts run first — and why headless mode is the loudest signal.

Chromium Google Chrome Patchright CloakBrowser

Run on production on 2026-09-30: ✓ Node.js ✓ Python ✓ Java ✓ C# ✓ Go

The problem

cdpfleet offers several Chromium builds with different anti-detection work. Which of them pass the checks a typical detection script runs in its first milliseconds — navigator.webdriver, the user agent, plugins, WebGL renderer, permission consistency, window chrome? And how much does headless mode change the picture?

What we used, and why

WhatWhy
chromiumPlain open-source Chromium: the baseline.
chromeReal Google Chrome (stable).
patchrightChromium with Patchright's automation-hiding patches.
cloakbrowserChromium with CloakBrowser's source-level fingerprint patches.
headless: true / falseHeadless costs 1 thread, headful (a virtual display) 2. Each build runs both.
A page <script> via page.routeThe checks run as a real page script on an https origin, the way a website runs them — not through page.evaluate.

How it works

  1. Launch all eight combinations (4 builds × headless/headful) at once.
  2. Serve a small detection page with route.fulfill on an https URL and let its script run.
  3. Collect what the script found.

The code

The same program in five languages (also on GitHub, with the raw output). Set these environment variables first:

// npm install [email protected]
// env: CDPFLEET_API_KEY, PROXY_URL
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;
const BUILDS = ['chromium', 'chrome', 'patchright', 'cloakbrowser'];

// The checks a bot-detection script typically runs, as a page script (not page.evaluate),
// exactly as a website would run them.
const CHECKS = `<script>
window.__checks = (async () => {
  const gl = document.createElement('canvas').getContext('webgl');
  const dbg = gl && gl.getExtension('WEBGL_debug_renderer_info');
  const perm = await navigator.permissions.query({ name: 'notifications' });
  return {
    webdriver: navigator.webdriver,
    headless_in_ua: /Headless/.test(navigator.userAgent),
    window_chrome: typeof window.chrome === 'object',
    plugins: navigator.plugins.length,
    languages: navigator.languages.join(','),
    webgl_renderer: dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : null,
    notification_permission_mismatch: Notification.permission === 'denied' && perm.state === 'prompt',
    outer_minus_inner_height: outerHeight - innerHeight,
  };
})();
</script>`;

async function check(name, headless) {
  const res = await fetch(`https://starter.cdpfleet.com/${name}/session`, {
    method: 'POST',
    headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
    body: JSON.stringify({ proxy: process.env.PROXY_URL, headless }),
  });
  const mode = headless ? 'headless' : 'headful';
  if (!res.ok) return { build: name, mode, error: `${res.status} ${await res.text()}` };
  const { wsUrl } = await res.json();
  const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
  try {
    const page = await browser.newPage();
    // A real https origin: some APIs (permissions, WebGL info) behave differently on about:blank.
    await page.route('https://detect.example/', (route) => route.fulfill({ contentType: 'text/html', body: CHECKS }));
    await page.goto('https://detect.example/');
    return { build: name, mode, version: browser.version(), ...(await page.evaluate(() => window.__checks)) };
  } finally {
    await browser.close();
  }
}

// Every build twice: headless (1 thread) and headful on a virtual display (2 threads).
console.log(JSON.stringify(await Promise.all(BUILDS.flatMap((b) => [check(b, true), check(b, false)])), null, 2));

What we got

BuildModewebdriverHeadless in UAPluginsPermission mismatchWebGL renderer
chromiumheadlessnoyes0yesANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)
chromiumheadfulnono5noANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)
chromeheadlessnoyes5noANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)
chromeheadfulnono5noANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)
patchrightheadlessnoyes0yesANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)
patchrightheadfulnono5no—
cloakbrowserheadlessnono5noANGLE (NVIDIA Corporation, NVIDIA GeForce RTX 3080/PCIe/SSE2, OpenGL 4.5.0 NVIDIA 565.77)
cloakbrowserheadfulnono5noANGLE (NVIDIA Corporation, NVIDIA GeForce RTX 5070 Ti Laptop GPU/PCIe/SSE2, OpenGL 4.5.0 NVIDIA 565.77)

From the Node.js run on 2026-09-30. IP addresses are replaced with placeholders (203.0.113.x); equal addresses stay equal. The other languages produced the same findings.

Raw output (Node.js)
[
  {
    "build": "chromium",
    "mode": "headless",
    "version": "148.0.7778.96",
    "webdriver": false,
    "headless_in_ua": true,
    "window_chrome": false,
    "plugins": 0,
    "languages": "en-US",
    "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
    "notification_permission_mismatch": true,
    "outer_minus_inner_height": 0
  },
  {
    "build": "chromium",
    "mode": "headful",
    "version": "148.0.7778.96",
    "webdriver": false,
    "headless_in_ua": false,
    "window_chrome": true,
    "plugins": 5,
    "languages": "en-US",
    "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
    "notification_permission_mismatch": false,
    "outer_minus_inner_height": 85
  },
  {
    "build": "chrome",
    "mode": "headless",
    "version": "153.0.8010.36",
    "webdriver": false,
    "headless_in_ua": true,
    "window_chrome": true,
    "plugins": 5,
    "languages": "en-US,en",
    "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
    "notification_permission_mismatch": false,
    "outer_minus_inner_height": 0
  },
  {
    "build": "chrome",
    "mode": "headful",
    "version": "153.0.8010.36",
    "webdriver": false,
    "headless_in_ua": false,
    "window_chrome": true,
    "plugins": 5,
    "languages": "en-US,en",
    "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
    "notification_permission_mismatch": false,
    "outer_minus_inner_height": 85
  },
  {
    "build": "patchright",
    "mode": "headless",
    "version": "148.0.7778.96",
    "webdriver": false,
    "headless_in_ua": true,
    "window_chrome": false,
    "plugins": 0,
    "languages": "en-US",
    "webgl_renderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
    "notification_permission_mismatch": true,
    "outer_minus_inner_height": 0
  },
  {
    "build": "patchright",
    "mode": "headful",
    "version": "148.0.7778.96",
    "webdriver": false,
    "headless_in_ua": false,
    "window_chrome": true,
    "plugins": 5,
    "languages": "en-US",
    "webgl_renderer": null,
    "notification_permission_mismatch": false,
    "outer_minus_inner_height": 85
  },
  {
    "build": "cloakbrowser",
    "mode": "headless",
    "version": "146.0.7680.177",
    "webdriver": false,
    "headless_in_ua": false,
    "window_chrome": true,
    "plugins": 5,
    "languages": "en-US",
    "webgl_renderer": "ANGLE (NVIDIA Corporation, NVIDIA GeForce RTX 3080/PCIe/SSE2, OpenGL 4.5.0 NVIDIA 565.77)",
    "notification_permission_mismatch": false,
    "outer_minus_inner_height": 85
  },
  {
    "build": "cloakbrowser",
    "mode": "headful",
    "version": "146.0.7680.177",
    "webdriver": false,
    "headless_in_ua": false,
    "window_chrome": true,
    "plugins": 5,
    "languages": "en-US",
    "webgl_renderer": "ANGLE (NVIDIA Corporation, NVIDIA GeForce RTX 5070 Ti Laptop GPU/PCIe/SSE2, OpenGL 4.5.0 NVIDIA 565.77)",
    "notification_permission_mismatch": false,
    "outer_minus_inner_height": 85
  }
]

Takeaways