Skip to main content
Blog
Blog

Headless Browser Detection: Signals, Methods, and What Works in 2026

Headless browsers power most bot attacks in 2026. Which signals catch them, why simple checks fail against stealth tooling, and what holds.

Jul 06, 2026 10 min read
Headless Browser Detection: Signals, Methods, and What Works in 2026
Table of Contents

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.

TL;DR: headless browser detection

Headless browser detection in 2026 works in four layers, ordered by how hard each is to defeat: (1) API checks like navigator.webdriver, trivially patched; (2) rendering and GPU fingerprints, harder to spoof; (3) TLS and HTTP/2 transport fingerprints, require modified browser builds; (4) behavioral motion, no automation library has replicated it reliably at scale. cside's cursor_v2 catches 98.2% of raw Playwright sessions and 100% of stealth-mode browserless.io sessions at a false-positive rate under 1%.

Short on time? See cside's AI-agent detection. It covers everything below in one deployment.

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 true by WebDriver. Stealth libraries overwrite this to return undefined in 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.chrome with several nested methods (csi(), loadTimes(), runtime). Headless environments historically returned a sparse or missing chrome object. Stealth patches reconstruct it.
  • Permissions API: In a real browser session opened fresh, the Permissions API returns 'prompt' for notifications. 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 TLS handshake fingerprint or TLS handshake fingerprint 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.

How to Detect Selenium Bots

Selenium is the most widely deployed browser automation framework and leaves a more visible signature than modern stealth tools. Three signals are the most reliable starting point.

navigator.webdriver returning true is the most basic check. Unmodified Selenium sessions expose this flag by design. It only catches bots with no evasion layer, because selenium-stealth patches it in a few lines. Use it as a first gate, not a final verdict.

Chrome DevTools Protocol (CDP) artifacts are harder to patch. Selenium drives Chrome over CDP, and the connection leaves traces: the chrome.debugger API surface differs from a real user session, CDP-specific event signatures appear in the performance timeline, and layout and paint events fire with characteristic timing offsets. These survive most surface-level evasion patches because they reflect the CDP transport layer, not JavaScript-accessible APIs.

Cross-signal mismatches catch sessions that patch individual checks but do not maintain internal consistency. A UA string claiming Chrome 120 on Windows while WebGL reports SwiftShader, or while the TLS fingerprint matches a headless Linux build, is contradictory in a way no real browser produces. The mismatch is the signal.

Against an unmodified Selenium session, these three checks resolve the detection at Layer 1. Against a stealth-patched session, Layer 3 (TLS fingerprint) and Layer 4 (behavioral scoring) finish the job. A Selenium script that patches its API surface still cannot make mouse events look like a real hand.

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.

Related reading: our full guide to detecting AI agent traffic on your website · stealth browsers and anti-detect browsers, explained

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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 250 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.

Further reading

Avneh Bhatia
AI Researcher

Making machines learn. Applied math major currently developing the next generation of bot detection models at cside.

FAQ

Frequently Asked Questions

Headless browser detection is the practice of identifying browser sessions that are driven by automation frameworks (such as Playwright, Puppeteer, or Selenium) rather than by a real human. Detection systems analyze signals across browser APIs, rendering behavior, network fingerprints, and user interaction patterns to distinguish automated sessions from genuine visitors.

At the API layer: navigator.webdriver being set to true, missing or incomplete window.chrome properties, zero-length plugin arrays, and Permissions API returning 'denied' for notifications in a fresh session. At the rendering layer: WebGL reporting a SwiftShader or Mesa renderer instead of a real GPU. At the network layer: a TLS fingerprint (TLS handshake fingerprint/TLS handshake fingerprint) that matches Chromium headless rather than a user-installed browser. At the behavioral layer: cursor paths with no natural micro-corrections, absent scroll jitter, and sub-millisecond timing consistency that no human hand produces.

No. navigator.webdriver is the most widely patched signal. Libraries like puppeteer-extra-plugin-stealth overwrite it to return undefined, so it only catches unsophisticated bots running bare Playwright or Selenium with no evasion layer. Treating it as a primary signal produces low recall against modern stealth tooling.

Stealth browsers (Camoufox, Multilogin, puppeteer-extra-plugin-stealth, anti-detect browser products) patch dozens of API-level signals and can pass most browser-fingerprint checks. They are harder to catch at the API and rendering layers. However, behavioral signals - particularly cursor motion, scroll rhythm, and interaction timing - cannot be patched at the API level. Stealth tooling changes what the browser reports, not how a script actually moves through a page, which is where behavioral detection holds up.

Bot detection is the broader category: it covers headless browsers, browser emulators, server-side scrapers, credential-stuffing scripts, and any automated client. Headless browser detection is a specific method within bot detection that focuses on identifying browsers running without a visible GUI - the tool category most commonly used for scraping, account fraud, and checkout automation. Modern bot detection layers headless-browser signals on top of network, behavioral, and reputation signals to increase coverage.

cside runs client-side monitoring in real visitors' browsers and collects signals across 250+ dimensions covering network behavior, browser API state, rendering output, and cursor and scroll behavior. The detection stack combines deterministic API checks with a behavioral model (cursor_v2) that scores mouse motion against patterns no automation library has successfully replicated at scale. On controlled tests, raw Playwright sessions are caught at 98.2% recall and stealth-mode browserless sessions at 100%, at a human false-positive rate under 1%.

Headless browsers such as Puppeteer, Playwright, and headless Chrome render real pages and can pass challenges that assume a scripted, non-browser client, while rotating IPs to defeat rate limits. They are caught by the browser and runtime signals they cannot fully hide. cside draws on 250+ browser, device, and network signals to flag headless and automated browsers that IP-based and challenge-based tools wave through.

The most reliable approach layers signals instead of relying on one check. Start with fast network and TLS fingerprint filtering, add browser fingerprint scoring after JavaScript runs, then finish with behavioral scoring of cursor motion, scroll rhythm, and interaction timing. Scraping and account-fraud bots often patch their fingerprints, so behavioral signals are what hold. cside combines all four layers and uses 250+ signals per session to flag automation that single-signal tools miss.

Behavioral detection is the layer that works when fingerprint checks pass. Stealth browsers like Camoufox, Multilogin, and puppeteer-extra-plugin-stealth patch API, rendering, and identity signals, but they cannot change how a script actually moves through a page. Methods that score cursor motion, scroll velocity, and timing distributions catch these sessions where API and rendering checks wave them through. cside's cursor_v2 model catches stealth-mode browserless sessions at 100% in controlled tests.

Weigh three things: signal depth, evasion resistance, and deployment friction. A tool that leans on navigator.webdriver or IP reputation alone will miss stealth tooling, so look for behavioral scoring layered on top of fingerprint and network checks. Check the false-positive rate on real humans, since aggressive blocking costs conversions. Confirm it deploys without a proxy or DNS change. cside runs as one first-party script and scores 250+ signals per session, including cursor and scroll behavior.

cside deploys as a single first-party JavaScript snippet with no proxy and no DNS change. Once the script runs in your visitors' browsers, it collects 250+ signals per session across network, browser API, rendering, and behavioral dimensions, then scores each session for automation. An agentless Scan Method is also available. You can start on the free plan and talk to the team for higher-volume needs. Detection is available through cside bot detection and AI agent detection.

cside prices on usage rather than a flat license. There is a free plan to start, metered tiers based on session or pageview volume as you grow, and a talk-to-the-team option for large or custom deployments. Because cside deploys as one first-party script with no proxy or DNS change, there is no separate infrastructure cost to route traffic. For current tiers, check the cside pricing page or contact the team.

Monitor and Secure Your Third-Party Scripts

Gain full visibility and control over every script delivered to your users to enhance site security and performance.

Start free, or try Business with a 14-day trial.

cside dashboard interface showing script monitoring and security analytics
Related Articles
Book a demo

Want to walk through this with an engineer?

Thirty minutes, on your own site. Not a slide deck.

We'll show you:

Which third-party scripts are running on your site right now
Where you stand on PCI DSS 6.4.3 and 11.6.1
How much of your traffic is bots and AI agents

Rather just send a question?

Finding open slots…

Real humans only. We'd know.

Having trouble booking? Open scheduler in a new tab

What are you trying to solve?

Tell us in a line and we'll come back with something useful, not a generic pitch.

We usually help with:

Seeing which third-party scripts run on your site
PCI DSS 6.4.3 and 11.6.1 evidence
Bots, AI agents and account takeover

Prefer to just book a time? Pick a slot instead