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. A "headless" browser is a real browser running with no visible window, usually driven by code; "instrumented" means it is being controlled by an automation tool. Plain Puppeteer or Playwright leaks at least a dozen detectable signals out of the box — navigator.webdriver set to true (a flag browsers raise when automation is in control), a missing chrome.runtime object, a predictable plugins list, and blank or always-identical canvas output (the image a page draws to fingerprint your graphics stack). 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

These are the giveaways a detector can catch with a single line of JavaScript. By default, Puppeteer and Playwright launch with navigator.webdriver === true (the automation flag is on), window.chrome missing or stripped out, an empty navigator.plugins list, and an HTTP language header that does not match what navigator.languages reports inside the page. 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 (fake convincingly):

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

Here is the key idea. A stealth plugin patches 5–10 of these from inside JavaScript — but the Function.toString() check listed above defeats every JS-layer patch at once, because in JavaScript you can ask any function to print its own source code. A real browser API prints [native code]; a patched one prints the replacement, exposing the patch. Patching below JavaScript, inside the browser's own C++ engine (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 simple: are the patches 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 baked into the browser binary and is invisible to JavaScript inspection. Production scraping in 2026 picks tools from the bottom of this list.

The medium tells

These take a little more work to catch than a one-line flag check. Headless Chrome ships with a slightly different default set of fonts than desktop Chrome. The HeadlessChrome string appears in the user agent unless you override it. Asking the graphics API for its hardware name, WebGLRenderingContext.getParameter(UNMASKED_RENDERER), returns "Google SwiftShader" (a software renderer, i.e. no real GPU) on headless instead of a genuine GPU name. The permissions API returns "denied" for notifications without ever prompting the user. Each of these is patched in modern stealth tools.

The hard tells (2026)

The current frontier is meta-detection — catching the act of patching itself rather than any one fingerprint. Anti-bot systems call Function.prototype.toString() on patched native APIs to see whether they return function () { [native code] } (a genuine browser function) or the stealth tool's replacement code (a giveaway). playwright-stealth fails this check; Kasada catalogs the patch signatures and blocks on a match. The 2026 answer is to patch the browser source itself (Camoufox, PatchRight) so there is nothing in the JavaScript 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 window, driven entirely by code instead …
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 fork of Firefox with anti-fingerprinting patches applied at the C++ build level. That phrase matters: most anti-fingerprinting…
What Is PatchRight?
PatchRight is a browser-automation library that edits Playwright's own Python code before Chrome launches, instead of injecting JavaScript i…
How Do Websites Detect Web Scrapers?
Websites spot scrapers by gathering hundreds of small clues about each visitor, then scoring how human the whole picture looks. No single cl…
What Is the Chrome DevTools Protocol (CDP)?
The Chrome DevTools Protocol (CDP) is the low-level interface for instrumenting and controlling Chromium-based browsers. Low-level means it …
What Is WebGL Fingerprinting?
WebGL fingerprinting reads identifying information directly from the GPU. WebGL is the browser feature that lets web pages draw 3D graphics …
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 a technique anti-bot scripts use to identify JavaScript functions that have been modified at run…
What Is Playwright?
Playwright is a cross-browser automation framework from Microsoft that drives Chromium, Firefox, and WebKit through a single API. An automat…
What Is Selenium?
Selenium is the original cross-browser automation framework — the W3C WebDriver standard predates Puppeteer by a decade. In plain terms, it …
Anti-Bot Vendor Detection Cheatsheet
A useful first step when working with any protected site you are authorized to access is identifying which anti-bot vendor sits in front of …
What Is a WebRTC IP Leak?
A WebRTC IP leak is when your browser quietly reveals your real IP address — even though you set up a proxy to hide it. It is the most-overl…
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 framework for automating and testing browsers, built on top of Selenium 4. Its two notable features, UC Mode and CD…
What Is Botasaurus?
Botasaurus is a free, open-source (MIT-licensed) Python framework for building web scrapers. You wrap your scraping functions with one of th…
What Is XDriver?
XDriver is a browser-automation tool for Playwright (a browser-automation library): one command swaps Playwright's internal driver files for…
What Is CloakBrowser?
CloakBrowser is a Chromium build with 49 C++ binary patches that give it a consistent browser configuration. The goal is for it to present l…
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. A headless browse…
Anti-Detect Browser Tools Compared
Anti-detect browser tools aim to present a consistent, real-looking browser configuration so that automated sessions render the same fingerp…
What Is WebGPU Fingerprinting?
WebGPU fingerprinting reads identifying data from the modern navigator.gpu API. WebGPU is the newest browser standard for talking to your GP…
What Is Client Hints Fingerprinting?
User-Agent Client Hints (UA-CH) are a set of structured HTTP headers plus a matching JavaScript API that report the same browser and operati…
What Is a Timezone / IP Mismatch?
A timezone/IP mismatch is when the location a browser claims and the location of its IP address disagree. Anti-bot systems (the software sit…
What Is navigator.webdriver?
navigator.webdriver is a standardized boolean that returns true when the browser is being controlled by automation. Think of it as a built-i…
What Is JA3 Fingerprinting?
JA3 is a method for fingerprinting a TLS client by hashing the fields of its Client Hello. TLS is the encryption layer behind https, and the…
What Is HTTP/3 / QUIC Fingerprinting?
HTTP/3 / QUIC fingerprinting identifies a client from the QUIC transport layer that HTTP/3 runs on. QUIC is the modern transport beneath HTT…
What Is Hardware Fingerprinting?
Hardware fingerprinting reads device capability signals - CPU cores, RAM, and screen metrics - that JavaScript exposes directly. These are v…
What Is CDP Detection?
CDP detection is the family of techniques anti-bot scripts use to tell that a browser is being driven through the Chrome DevTools Protocol (…
What Is Incognito Detection?
Incognito detection is the set of techniques that reveal whether a browser is in private / incognito mode. Private mode is the browser featu…
What Is Media Devices Fingerprinting?
Media devices fingerprinting reads the list of cameras, microphones, and speakers a browser reports via navigator.mediaDevices.enumerateDevi…
What Is Speech Synthesis Fingerprinting?
Speech synthesis fingerprinting reads the list of text-to-speech voices exposed by window.speechSynthesis.getVoices(). "Text-to-speech" mean…
What Is Stack Depth Fingerprinting?
Stack depth fingerprinting measures the maximum JavaScript recursion depth a browser allows before throwing a RangeError: Maximum call stack…
What Is CSS Media Query Fingerprinting?
CSS media query fingerprinting reads operating-system and device preferences through window.matchMedia(). A media query is a yes/no question…
What Is Screen Resolution Fingerprinting?
Screen resolution fingerprinting reads the display measurements a browser reports - screen.width/height, availWidth/availHeight, colorDepth,…
Browser Automation Engine Benchmarks
A browser-automation-engine benchmark drives several automation stacks through the same set of targets and records, side by side, how often …
How Do You Choose an Anti-Detect Browser Tool?
Choosing an anti-detect browser tool comes down to matching the tool's strengths to the detection layer you actually face - no single tool i…
What Is JavaScript Rendering?
JavaScript rendering is the process of executing a page's JavaScript in a real browser engine so that content built on the client side appea…
How Do You Instrument a Browser to Study Anti-Bot Scripts?
Instrumenting a browser means adding observation points so you can watch exactly which APIs a page calls -- which is how researchers study f…

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 — its patch signatures are catalogued and detected through Function.toString() inspection (asking a function to print its source and noticing it is not native browser code).

Is Camoufox detectable?

Less so than stealth-patched Chromium. It is a Firefox fork with anti-fingerprinting built into the source code, so there are no runtime patches for toString() to find. It can still be caught through behavioral signals — like the absence of mouse movement — 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 running a real user profile through CDP (Chrome DevTools Protocol, the wire interface tools use to drive Chrome). For high 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?

Yes, on sites with no anti-bot protection — most internal tools, documentation sites, marketing landing pages, and small e-commerce. Past roughly 10% of the public sites you encounter, you hit detection of some kind; even Cloudflare's Bot Fight Mode blocks vanilla Playwright using the datacenter-IP heuristic (flagging traffic from server data centers rather than home connections). For anything advertised as production scraping, start with a patched variant.

Last updated: 2026-05-31