Anti-Bot

What Is Headless Browser Detection?

What Is Headless Browser Detection? — conceptual illustration
On this page

Headless browser detection is the set of probes anti-bot systems use to distinguish a headless or instrumented Chrome session from a real user's browser. Plain Puppeteer or Playwright leaks at least a dozen detectable signals out of the box — navigator.webdriver true, missing chrome.runtime, predictable plugins array, blank or deterministic canvas output. Stealth patches close most of the easy tells; the hard ones survive into 2026.

Quick facts

Easiest tellnavigator.webdriver === true
Common tellsMissing chrome.runtime, plugin array length, languages mismatch
Hard tellsCanvas/WebGL fingerprint, Function.toString() on patched APIs
Stealth toolsplaywright-stealth, puppeteer-extra-plugin-stealth, Camoufox, PatchRight
2026 realityStealth patches that leave toString() trails (Kasada catches these)

The easy tells

Default Puppeteer/Playwright launches with navigator.webdriver === true, window.chrome missing or stripped, an empty navigator.plugins, and an HTTP language list that does not match navigator.languages. Any of these is a single-line check. Stealth plugins patch all of them — but the patches themselves are detectable (see below).

The complete signal inventory

Twelve signals modern anti-bot scripts inspect for headless detection, grouped by how cheap they are to spoof:

SignalWhat's checkedSpoof cost
navigator.webdriver=== true in unmodified Playwright/Puppeteer/SeleniumTrivial JS override (but see toString check below)
User-Agent "HeadlessChrome"Default headless Chrome substringTrivial — one line
navigator.pluginsEmpty array in default headlessTrivial JS override
navigator.languagesLength 1 in default headless vs typical 2-3Trivial
WebGL rendererSwiftShader / llvmpipe = no GPUMedium — engine-level patch needed
AudioContext fingerprint~3 known headless-Linux hashesMedium — virtual audio device or engine patch
Canvas fingerprintStable per-machine; headless on Linux produces a small clusterHard — PerfectCanvas replay required
CDP runtime artifactswindow.cdc_* keys, Runtime.evaluate timingHard — undetected-chromedriver patches, breaks on update
Function.toString() inspectionEvery JS-patched method returns its source, not [native code]Very hard — needs engine-level patch
Permissions API quirksNotification.permission === 'default' in headless on a denied-notifications profileMedium
Mouse + scroll absenceZero mouse events before clickMedium — synthesize Bezier-curve movement
requestAnimationFrame cadenceHeadless renders at fixed 60Hz with no vsync jitterHard — engine-level

The cumulative point: a stealth plugin patches 5–10 of these at the JS layer, but the toString() check above defeats every JS-layer patch simultaneously. Engine-level patching (Camoufox, CloakBrowser, PatchRight) is the only durable answer in 2026.

Toolchain status in 2026

Where each stealth toolchain stands against the 12-signal inventory above:

ToolApproachDefeats
Vanilla Playwright/PuppeteerNoneNothing — block-grade on first request
puppeteer-extra-stealthJS-layer patches (~17)Easy tells; loses to toString inspection
undetected-chromedriverBinary + JS patchesEasy + medium tells; loses to toString and Canvas
SeleniumBase UC modeWraps UC; adds Turnstile auto-clickSame as UC, friendlier API
PatchRightPatches Playwright Python source — patches never exist as JSEasy + medium + toString. Loses to deep Canvas/WebGL only at enterprise tier.
CamoufoxFirefox fork with C++ engine patches + real-machine profile DBAll 12 signals. Hyphenation-dictionary check can still expose it as Firefox.
CloakBrowserChromium fork with 49 C++ patchesAll 12 signals. reCAPTCHA v3 ~0.9 score.

The dividing line is whether the patches live above or below the JavaScript engine. Above (Playwright/Puppeteer-extra/UC/SeleniumBase) means Function.toString() can read the patch and detect it. Below (PatchRight/Camoufox/CloakBrowser) means the patch is invisible to JS reflection. Production scraping in 2026 picks tools from the bottom of this list.

The medium tells

Headless Chrome ships with a slightly different default font set than desktop Chrome. The HeadlessChrome string appears in the user agent unless overridden. WebGLRenderingContext.getParameter(UNMASKED_RENDERER) returns "Google SwiftShader" on headless instead of a real GPU string. The permissions API returns "denied" for notifications without prompting. Each of these is patched in modern stealth tools.

The hard tells (2026)

The current frontier is meta-detection: anti-bot systems call Function.prototype.toString() on patched native APIs to see if they return function () { [native code] } or the stealth tool's replacement code. playwright-stealth fails this check; Kasada catalogs the patch signatures and blocks on match. The 2026 answer is to patch the browser source (Camoufox, PatchRight) so there is nothing in the JS runtime for toString() to inspect.

Code example

javascript
// Detect headless from the page side
const tells = {
  webdriver: navigator.webdriver,
  pluginCount: navigator.plugins.length,
  chromeRuntime: typeof window.chrome?.runtime,
  webglVendor: (() => {
    const gl = document.createElement('canvas').getContext('webgl');
    const ext = gl?.getExtension('WEBGL_debug_renderer_info');
    return gl?.getParameter(ext?.UNMASKED_RENDERER_WEBGL);
  })()
};
// Real Chrome: webdriver=false, plugins>0, chrome.runtime='object', GPU string
// Headless: webdriver=true, plugins=0, chrome.runtime='undefined', 'SwiftShader'

Related terms

What Is a Headless Browser?
A headless browser is a real web browser — Chrome, Firefox, or WebKit — that runs without a visible graphical interface, controlled entirely…
What Is Browser Fingerprinting?
Browser fingerprinting is a technique that identifies and tracks a visitor by combining dozens of small, observable characteristics of their…
What Is Camoufox?
Camoufox is a stealth-focused fork of Firefox with anti-fingerprinting patches applied at the C++ build level. Unlike playwright-stealth, wh…
What Is PatchRight?
PatchRight is a stealth library that patches the Playwright Python source itself before Chrome starts, rather than injecting JavaScript at r…
How Do Websites Detect Web Scrapers?
Websites detect scrapers by collecting hundreds of signals across the network, transport, browser, and behavioral layers, then scoring the c…
What Is the Chrome DevTools Protocol (CDP)?
The Chrome DevTools Protocol (CDP) is the low-level interface for instrumenting and controlling Chromium-based browsers. Puppeteer, Playwrig…
What Is WebGL Fingerprinting?
WebGL fingerprinting reads identifying information directly from the GPU. The browser exposes the graphics card vendor and renderer string (…
What Is AudioContext Fingerprinting?
AudioContext fingerprinting plays a silent waveform through the Web Audio API, then reads back the resulting floating-point samples and hash…
What Is Function.toString() Inspection?
Function.prototype.toString() inspection is the technique anti-bot scripts use to detect runtime JavaScript patches. Every JS function expos…
What Is Playwright?
Playwright is a cross-browser automation framework from Microsoft that drives Chromium, Firefox, and WebKit through a single API. Released i…
What Is Selenium?
Selenium is the original cross-browser automation framework — the W3C WebDriver standard predates Puppeteer by a decade. It drives Chrome, F…
Anti-Bot Vendor Detection Cheatsheet
The first step of any scrape against a protected site is identifying which anti-bot vendor is in front of it. The vendor determines almost e…
What Is a WebRTC IP Leak?
A WebRTC IP leak is the most-overlooked failure mode in browser-based scraping in 2026: WebRTC reveals your real local and public IP via STU…
What Is Fingerprint Lie Detection?
Fingerprint lie detection is the practice of verifying that the signals a browser reports are internally consistent and untampered, rather t…
What Is Timing & Cache Side-Channel Fingerprinting?
Timing-based fingerprinting uses high-resolution clocks to measure how long operations take, turning microarchitectural and rendering behavi…
What Is SeleniumBase?
SeleniumBase is a Python automation and testing framework built on Selenium 4 whose UC Mode and CDP Mode make it one of the most effective P…
What Is Botasaurus?
Botasaurus is an MIT-licensed Python scraping framework with three top-level decorators — @browser, @request, @task — and built-in Bezier-cu…
What Is XDriver?
XDriver is a Playwright stealth patcher that replaces Playwright's driver files in place with hardened versions, activated by a single comma…
What Is CloakBrowser?
CloakBrowser is a stealth Chromium build with 49 C++ binary patches. Where playwright-stealth injects JavaScript at runtime (detectable via …
What Is Scrapling?
Scrapling is an all-in-one Python scraping framework that bundles fetching, parsing, anti-detection, and crawling behind one API — it is a l…
What Is Obscura?
Obscura is an open-source headless browser engine written from scratch in Rust — not a fork or patch of Chrome or Firefox. It runs JavaScrip…
Anti-Detect Browser Tools Compared
Anti-detect browser tools defeat bot detection by spoofing the signals that distinguish automation from a real user — but they work at very …

Concept map

How Headless Browser Detection connects

The terms most directly tied to this one. Hover a node to see its neighbours, click to preview, drag to rearrange.

0 terms · 0 connections
You are here · Anti-Bot
Building map…

Frequently asked questions

Does playwright-stealth actually work?

For soft targets, yes. Against Cloudflare bot management, Kasada, or DataDome, no — the patch signatures are catalogued and detected through Function.toString() inspection.

Is Camoufox detectable?

Less so than stealth-patched Chromium. It is a Firefox fork with anti-fingerprinting built into the source, so there are no runtime patches for toString() to find. Still detectable through behavioral signals if you do not emulate them.

Should I just use a real browser session via CDP?

For low volume, yes — attach to a normal Chrome with a real user profile via CDP. For volume, the operational cost of managing real browsers outweighs the detection cost of a hardened headless setup or a managed scraping API.

Is there ever a case where vanilla Playwright is enough?

Sites with no anti-bot protection — most internal tools, documentation sites, marketing landing pages, small e-commerce. Past ~10% of public sites you encounter detection of some kind, and even Bot Fight Mode blocks vanilla Playwright via the datacenter-IP heuristic. For anything advertised as production scraping, start with a patched variant.

Last updated: 2026-05-27