Most bot attacks today run inside a real browser engine. Playwright, Puppeteer, Selenium, and commercial headless browser APIs all use Chromium or Firefox under the hood, which means the classic signs of automation - a user-agent string that reads python-requests or a missing Accept-Language header - are gone. Detection has to happen at a different layer.
This guide covers how headless browser detection actually works in 2026: the signal hierarchy from browser API checks through rendering fingerprints to behavioral scoring, why simple checks fail against stealth tooling, and what holds up when the fingerprint is clean.
What is a headless browser?
A headless browser is a browser engine (typically Chromium or Firefox) running without a display. The same rendering, JavaScript, and networking stack that powers Chrome handles every request, but no GUI is painted. Playwright, Puppeteer, Selenium WebDriver, and commercial APIs like browserless.io all drive headless Chromium this way.
Headless browsers are legitimate tools for end-to-end testing, screenshot generation, and CI pipelines. They are also the dominant automation method for price scraping, credential stuffing, account creation fraud, scalper bots targeting limited-inventory purchases, and AI agent workflows that interact with web applications. The detection problem exists because the same infrastructure serves both groups, and they are hard to tell apart by what the browser is rather than how it moves through a site.
The signal hierarchy
Detection systems layer signals in order of reliability and evasion cost. The cheapest signals are API checks any library can patch. The most durable are behavioral patterns that require actually moving like a person.
Layer 1: Browser API checks (patchable)
The original headless detection relied on properties that Chrome headless set differently from a user-installed Chrome:
- navigator.webdriver: Set to
trueby WebDriver. Stealth libraries overwrite this to returnundefinedin less than ten lines of code. It only catches bots running bare, unmodified automation. - navigator.plugins.length: Older headless Chrome returned an empty plugins array. Modern Chromium headless populates plugins, but the composition can still differ from a real user's browser profile.
- window.chrome: A real Chrome instance exposes
window.chromewith several nested methods (csi(),loadTimes(),runtime). Headless environments historically returned a sparse or missingchromeobject. Stealth patches reconstruct it. - Permissions API: In a real browser session opened fresh, the Permissions API returns
'prompt'fornotifications. In headless, it often returns'denied'. This check is bypassed by overriding the API, but bots running without that patch still expose it. - navigator.languages: Real browsers return a populated array matching the user's language settings. Headless defaults often produce
['en-US', 'en']regardless of the IP's geography, creating a mismatch with network-layer signals.
These checks catch unsophisticated bots. Against anything running puppeteer-extra-plugin-stealth or a purpose-built anti-detect browser, Layer 1 produces low recall.
Layer 2: Rendering and GPU fingerprints (harder to patch)
The rendering environment headless Chrome runs in differs from a GPU-accelerated user browser in ways that propagate into what WebGL and canvas report.
WebGL's UNMASKED_RENDERER_WEBGL string reflects the GPU driver. A real Chrome instance on a MacBook returns something like Apple M2. A headless instance without GPU passthrough returns Google SwiftShader or ANGLE (Google, Vulkan 1.3.0 (SwiftShader)). Spoofing this string requires intercepting the WebGL extension call, which some stealth tools do, but the rest of the GPU capability surface (supported extensions list, precision values, max texture size) still reflects the underlying software renderer and can be cross-checked against the claimed GPU string.
Canvas fingerprinting measures how the browser rasterizes text and shapes. The pixel values differ between rendering stacks: hardware-accelerated Chrome on a real OS with real fonts produces different output from SwiftShader-based headless Chrome on the same nominal platform. A well-calibrated canvas check survives moderate spoofing because spoofing one canvas output consistently while keeping the rest of the surface coherent is difficult to do without introducing new signals.
Layer 3: Network and transport fingerprints (durable)
TLS fingerprinting captures the structure of the TLS ClientHello: cipher suite ordering, extension list, elliptic curve preferences. Every distinct TLS stack has a characteristic fingerprint, sometimes called a JA3 or JA4 hash. Chromium headless has a different fingerprint from user-installed Chrome, which differs from Firefox, which differs from Safari.
HTTP/2 fingerprinting does the same at the HTTP layer: frame ordering, SETTINGS parameters, WINDOW_UPDATE values, and header ordering all reflect the client implementation. A headless Chromium session that patches its user-agent to look like Chrome on macOS still sends HTTP/2 frames in a pattern that matches the Chromium headless build, not the desktop build.
Network-layer fingerprints are difficult to spoof because they require patching the transport stack, not JavaScript. A small number of commercial services do this via modified browser builds, but the majority of headless automation does not.
Layer 4: Behavioral signals (hardest to fake)
Behavioral detection scores what happens during a session rather than what the browser reports about itself. The core observation: a script that calls mouse.move(x, y) cannot produce the noise pattern a real hand leaves behind. Real cursor motion has micro-corrections, acceleration and deceleration governed by Fitts's law, and tiny lateral drift that no off-the-shelf path generator reproduces accurately.
cside's cursor model, cursor_v2, is trained on real captured human sessions and scores a session's movement patterns against the distribution real hands produce. In controlled tests on Playwright-driven sessions, the model catches 98.2% of raw automation at a human false-positive rate under 1%. Stealth humanizer libraries like WindMouse and NaturalMouse produce curved paths, but the curvature geometry alone is not what the model grades. Humanlike browserless.io sessions (which use out-of-the-box curved-path motion) are caught at 100% in the same test. For the full methodology, see Catching Playwright and browserless bots by how the cursor moves.
Scroll behavior produces similar signals: real users scroll in short bursts with natural velocity profiles. Scripts that call window.scrollTo() or drive scroll via CDP produce flat or step-function velocity that has no equivalent in human interaction data.
Interaction timing is the third behavioral axis. A human reading a page introduces random delays governed by reading speed, attention, and motor latency. Scripts executing a checkout flow at full speed create timing distributions that sit far outside any recorded human behavior.
The stealth browser arms race
Purpose-built evasion tools try to close each of these gaps:
- puppeteer-extra-plugin-stealth: patches 19 known API-level signals. Effective against Layer 1 checks. Does not address rendering or behavioral signals.
- Camoufox: a Firefox-based stealth browser that patches the fingerprint surface at the browser level. Harder to catch on Layer 1 and 2 signals, particularly because Firefox's TLS and rendering stacks differ from Chromium-based headless tools.
- Anti-detect browsers (Multilogin, AdsPower, LinkenSphere): commercial products that inject a full profile - fonts, GPU strings, language, timezone, and hardware fingerprint - to make each session appear as a distinct real device. They target the identity layer of detection, not the behavioral layer.
- Residential proxies: change the network origin so IP reputation signals are clean. Do not affect browser fingerprints or behavioral signals.
None of these close the behavioral gap reliably at scale, because patching what the browser reports is different from making a script move like a person. Catching bots that don't want to be caught covers the two-stage neural stack cside runs for sessions that pass static fingerprint checks.
What detection looks like in practice
A production headless browser detection system does not rely on any single signal. The practical architecture is a sequence of gates:
- Fast deterministic checks at request time: user-agent structure, known datacenter ASNs, TLS fingerprint matching against a headless-browser hash list. These handle the bottom of the market with no ML cost.
- Browser fingerprint scoring after JavaScript has executed: API state coherence checks, rendering outputs, font enumeration, WebGL renderer cross-checks. These catch bots that have not patched their fingerprint.
- Behavioral scoring during the session: cursor motion, scroll events, timing distributions, form-fill patterns. These catch stealth tooling that has patched the fingerprint but cannot patch how a script actually navigates a page.
- Cross-session consistency: the same device fingerprint appearing across accounts that never share a login IP, the same cursor geometry appearing on sessions that claim to be different devices - consistency patterns that only arise from shared automation infrastructure.
The combination produces high recall against the full headless browser threat population, from bare Puppeteer at the bottom to commercial anti-detect browsers at the top, while keeping false-positive rates on real humans low enough to deploy without friction on live traffic.
How cside handles headless browser detection
cside deploys as a single first-party JavaScript snippet with no proxy or DNS change. It collects over 102 signals per session - browser API state, rendering outputs, network behavior, and cursor and scroll data - and runs a detection stack that combines rule-based filtering with the cursor_v2 behavioral model described above.
The client-side position matters for headless browser detection: cside sees what actually executes in the visitor's browser, including session-level behavior and real rendering outputs, not a crawled snapshot of what the page looks like from outside. A headless browser configured to serve clean responses to scanners still runs inside cside's collection environment and still produces the behavioral and rendering signals that give it away.
Detection is available through cside bot detection and cside AI agent detection. For context on how headless automation fits into the broader AI agent detection landscape, see bot detection: AI agents vs legacy tools and how OpenClaw agents bypass legacy controls.




