Skip to content
Blog

How to detect browser automation beyond user agents

How to catch browser automation when the user agent lies. Use TLS fingerprints, HTTP/2 signals, CDP artifacts, and behavior.

Frederick Jahn
Frederick JahnMarch 28, 2026
How to detect browser automation beyond user agents

User agents lie. These signals do not: TLS fingerprints, HTTP/2 parameters, CDP artifacts, and behavior.

What is browser automation?

Browser automation means a script drives a real browser instead of a human. The script opens pages. It clicks elements. It fills forms. It reads the DOM. It works through a control channel, usually the Chrome DevTools Protocol. The browser renders. The script decides. The site is supposed to tell the two apart. Usually it cannot.

Most bot detection starts with the user agent string. Every HTTP client sends this self-reported label with each request. Changing it takes one line of code. The W3C WebDriver spec says browsers must set navigator.webdriver to true when automation controls them. That was meant to be a reliable signal. It was not. The flaw showed up fast. When WebDriver bots learned they were caught through navigator.webdriver, they toggled it back to false.

Why browser automation matters right now

The scale is hard to overstate. Automated traffic reached 51% of all web traffic in 2024. Bots passed human visitors for the first time in a decade. Of the bad-bot traffic, 41% now counts as advanced. Advanced bots are built to mimic human behavior. They spoof everything the browser surface exposes, not just the user agent.

The signals a client volunteers cannot be trusted. So detection has to move to signals the client cannot control. At this volume, that shift is not optional. Only 2.8% of websites are fully protected against bots, down from 8.4% a year earlier. Among companies hit by account takeover attacks, 85% already had bot detection in place. Single-layer detection fades over time. The evasion ecosystem targets whatever the current standard is.

Types of automation

Four tooling families make up most scraping and agent traffic today.

Selenium. The oldest and most-taught framework. It drives browsers through ChromeDriver or GeckoDriver. Those talk to the browser through vendor automation protocols. Selenium leaves the loudest artifacts. ChromeDriver injects a $cdc_asdjflasutopfhvcZLmcfl_ variable into every page. One regex catches it.

Puppeteer. Google's Node.js library for driving Chromium through the Chrome DevTools Protocol. It ships with stealth plugins like puppeteer-extra-stealth. These overwrite obvious artifacts at page load. By early 2025, puppeteer-extra-stealth no longer reliably bypassed commercial bot detection. Maintainers paused active updates.

Playwright. Microsoft's multi-browser framework. It uses CDP for Chromium and vendor protocols for Firefox and WebKit. Forks like Patchright patch Playwright's CDP usage at the C++ layer. That avoids the side-effect events detectors watch for.

CDP-based and post-CDP frameworks. nodriver, zendriver (an async fork), and rebrowser-patches are the newest generation. Some minimize CDP use and avoid high-risk domains like Runtime and Console. Others drop CDP entirely. They drive the browser through OS-level input instead. No CDP session exists for a detector to notice.

How browser automation works

Puppeteer, Playwright, and Selenium all reach Chromium through one channel: the Chrome DevTools Protocol, or CDP. CDP is a WebSocket interface the browser exposes for debugging. Frameworks use it to drive clicks, run scripts, and read the DOM. About 95% of automated actions run through Page.evaluate. Page.evaluate is how CDP runs JavaScript inside the page.

Running Page.evaluate needs the CDP Runtime domain. Enabling that domain emits events the page can see. For years, detectors used one trick. They planted an error object with a custom getter on .stack. They called console.debug() on it. Then they watched for the getter to fire. A firing getter meant the Runtime domain was serializing the object across the WebSocket.

Then two V8 commits killed the trick in May 2025. The first, "Avoid error side effects in DevTools," landed on May 7. The second, "Apply getter guard throughout error preview," followed on May 9. Together they stopped the browser from running user-defined getters during error preview. The console.debug() check went from reliable to useless. Most vendors did not notice for months.

How to identify automation on your site

Detection has to move below the layer the script controls. Four signal classes survive modern automation.

TLS and protocol fingerprinting. A browser performs a TLS handshake before its first HTTP request. The ClientHello holds cipher suites, extensions, protocol versions, and ALPN preferences. The network stack sets these values, not any later JavaScript. JA4, built by FoxIO, resists the extension randomization that broke JA3. We track over 15 million unique JA4 fingerprints. They come from more than 500 million user agents.

HTTP/2 SETTINGS frame. When a connection opens, the client sends a SETTINGS frame. Its parameters vary by implementation. Chrome sends a WINDOW_UPDATE of about 15MB. Firefox sends about 12.5MB. Most HTTP libraries send zero or skip it. That is a 100x difference, visible before a single page byte moves. Pseudo-header order (:method, :authority, :scheme, :path) is hardcoded per browser. It does not match what libraries send by default.

JavaScript and CDP artifacts

JavaScript environment signals. Modern detection checks how properties are defined, not their values. When a stealth plugin overrides navigator.webdriver to return false, the property descriptor changes. The getter is no longer native code. It is a proxy. Calling toString() on it returns something subtly different from a real browser. Canvas and WebGL outputs come from the client's GPU. AudioContext processing depends on audio hardware. Tools in cloud environments produce outputs that match the cloud provider's hardware. That hardware does not match the consumer hardware their user agent claims.

CDP artifacts. When a CDP client connects and enables the Runtime domain, it triggers object serialization across the WebSocket. JavaScript in the page can observe this. A browser with no CDP client attached does not serialize this way. In October 2025 alone, we detected roughly 205,000 Puppeteer stealth events. We saw ten times more traffic from vanilla Selenium bots.

How to respond to automated traffic

Every detection technique has a countermeasure. TLS fingerprinting spawned curl-impersonate and tls-client. CDP detection spawned nodriver and rebrowser-patches. JavaScript fingerprinting spawned anti-detect browsers like Multilogin and GoLogin. These keep separate browser profiles with unique fingerprints. A response built on a single signal fades over time. The evasion ecosystem targets that one signal.

The response that survives the arms race is different. It checks consistency across layers and confirms with behavior. Say a request has a TLS fingerprint, HTTP/2 settings, and JavaScript environment that all match Chrome. Say its behavior looks human. That is probably a real browser. If any layer contradicts the others, it probably is not. The more layers you check at once, the more expensive evasion gets.

Behavioral analysis catches what fingerprinting cannot. Bots move the mouse in straight lines at constant speed. Direction locks to fixed angles. Human cursors wander. They follow curved paths at variable speed. They pause at odd moments. In controlled tests, keystroke timing alone classified bots with 99.98% accuracy. Bots type with mechanical consistency, or they inject random timing that follows a uniform distribution. That uniform pattern is itself a tell. These signals are strongest on interactive pages: login forms, search fields, and checkout flows.

Once you reach a verdict, three responses are open per request. Block automated traffic at the edge, so origin never pays for it. Challenge borderline sessions with a non-interactive test that forces the client to run code. Verify and allow the partner agents and search indexers you want, with a signed trust stamp and per-operator volume tracking.

Key takeaways

  • Browser automation makes up most of the bot traffic reaching modern sites. Bad bots were 37% of all internet traffic in 2024, up from 32% the year before.
  • User-agent filtering, IP reputation lists, and rate limiting catch only the laziest bots. The W3C navigator.webdriver flag flips back to false in one line.
  • Effective detection crosses layers. It cross-validates the TLS handshake, HTTP/2 SETTINGS, JavaScript environment, CDP artifacts, and behavior in real time. A contradiction between any two layers is the signal.
  • Response is a per-request choice: block, challenge, or verify. robots.txt handles well-behaved crawlers. Centinel checks every request across all these layers and decides in under 2ms, before the bot reaches origin.