Research · · 5 min read

How to detect Selenium bots?

How to detect Selenium bots?

Headless Chrome bots controlled by Selenium remain a staple in the bot developer’s toolkit in 2025. While newer frameworks like Playwright have gained traction, Selenium’s long-standing compatibility, extensive documentation, and integration with testing pipelines keep it popular, especially for automating login, signup, and scraping workflows at scale. It’s also commonly used in credential stuffing and fake account creation campaigns.

This article outlines how to detect Headless Chrome bots powered by Selenium, using a mix of HTTP header analysis and JavaScript browser fingerprinting signals. While Selenium supports multiple browsers, the focus here is on Chrome-based automation, which remains the most widely abused in practice.

TL;DR detection techniques:

If you just want to see the code, jump to the snippets below. The rest of this article explains how each technique works, and how attackers might try to bypass them.

We cover four key detection methods for Headless Chrome bots instrumented with Selenium:

Server-side detection (pseudocode):

// Server-side pseudocode to detect Selenium

// Test if the user agent HTTP header is linked to Headless Chrome
if (req.headers.get('user-agent').contains('HeadlessChrome')) {
	console.log('Headless Chrome detected!);
}

Client-side detection (JavaScript):

Let’s dive into each detection technique, starting with the most straightforward signal: the user agent string.

// 4 Efficient techniques to detect Headless Chrome bots instrumented with Selenium:

// Detection technique 1 based on the user agent
if (navigator.userAgent.includes("HeadlessChrome")) {
	console.log('Headless Chrome detected!);
}

// Detection technique 2 based on navigator.webdriver
if (navigator.webdriver) {
	console.log('Headless Chrome detected!);
}

// Detection technique 3 based on a serialization side effect of the Chrome Devtools Protocol
var e = new Error();
Object.defineProperty(e, 'stack', {
   get() {
    console.log('Headless Chrome detected!);
   }
});

// This is part of the detection, the console.log shouldn't be removed!
console.log(e);

// Detection technique 4 based on global variables exposed by Selenium
if ('cdc_adoQpoasnfa76pfcZLmcfl_Array' in window || 'cdc_adoQpoasnfa76pfcZLmcfl_Window' in window) {
		console.log('Selenium bot detected!')
}

Technique 1: Detecting unmodified Headless Chrome using the user agent

By default, Selenium bots running Headless Chrome send a user agent string that clearly reveals their automation context. A typical HTTP header set looks like this:

{
      'accept': '*/*',
      'accept-encoding': 'gzip, br',
      'accept-language': 'en-US,en;q=0.9',
      'priority': 'u=1, i',
      'sec-ch-ua': '"Google Chrome";v="137", "Chromium";v="137", "Not/A)Brand";v="24"',
      'sec-ch-ua-mobile': '?0',
      'sec-ch-ua-platform': '"macOS"',
      'sec-fetch-dest': 'empty',
      'sec-fetch-mode': 'cors',
      'sec-fetch-site': 'same-origin',
      'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/137.0.0.0 Safari/537.36'
    }

The key indicator here is the HeadlessChrome substring in the user-agent header:

  "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/137.0.0.0 Safari/537.36",

This string can be checked both server-side (via HTTP headers) and client-side (via JavaScript):

if (navigator.userAgent.includes("HeadlessChrome")) {
	console.log('Headless Chrome detected!);
}

Technique 2: Detecting modified Headless Chrome using navigator.webdriver

Once attackers patch the user agent to remove obvious indicators like the HeadlessChrome substring, the signal from HTTP headers disappears:

navigator.userAgent
// -> returns 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'

But automation frameworks like Selenium still leave other fingerprints. One of the most common is the navigator.webdriver property, which is set to true when a browser is automated. This property is accessible via JavaScript and is widely used for bot detection

if (navigator.webdriver) {
	console.log('Headless Chrome detected!);
}

While basic, this check remains effective against bots that don’t explicitly tamper with browser internals.

Technique 3: Detecting modified Headless Chrome bots using CDP

As you can imagine, most attackers also try to get rid of the navigator.webdriver = true property to avoid being detected as a bot.

The simplest way to erase this discriminating signal is to use the --disable-blink-features=AutomationControlled Chrome command line argument.

With this argument, the navigator.webdriver property doesn’t return true anymore:

navigator.webdriver
// -> returns false

Thus, to detect bots that modify their fingerprint, we need another approach. One of the most popular approaches in 2025 is called CDP detection. It leverages the fact that under the hood, Selenium uses CDP (Chrome DevTools Protocol) to communicate with Headless Chrome. During the communication process, Selenium needs to serialize data to send it with WebSocket, which may have unintended side effects. Thus, the propose of the JavaScript challenge below is to trigger an observable side effect when the CDP serialization occurs.

var e = new Error();
Object.defineProperty(e, 'stack', {
   get() {
       console.log('Headless Chrome detected!);
   }
});

// This is part of the detection, the console.log shouldn't be removed!
console.log(e);

If the console.log contained in the get function is called, it means that the error object was serialized, which happens only when CDP is used. Thus, it can be used to detect bot automation frameworks like Selenium that use CDP under the hood. Note that one of the side effects of this detection technique is that it will flag human users with dev tools open. The console.log(e) statement is part of the challenge since it is what triggers the serialization in CDP, it shouldn’t be removed.

Technique 4: Detecting Selenium via injected global variables

Selenium-driven browsers often leak telltale signs in the JavaScript execution context. By default, Selenium injects several global variables into the window object—typically with a cdc_ prefix, that can be used to detect its presence:

"cdc_adoQpoasnfa76pfcZLmcfl_Array"
"cdc_adoQpoasnfa76pfcZLmcfl_Object"
"cdc_adoQpoasnfa76pfcZLmcfl_Promise"
"cdc_adoQpoasnfa76pfcZLmcfl_Proxy"
"cdc_adoQpoasnfa76pfcZLmcfl_Symbol"
"cdc_adoQpoasnfa76pfcZLmcfl_JSON"
"cdc_adoQpoasnfa76pfcZLmcfl_Window"

Checking for the presence of any of these variables is a reliable way to fingerprint Selenium:

if ('cdc_adoQpoasnfa76pfcZLmcfl_Array' in window || 'cdc_adoQpoasnfa76pfcZLmcfl_Window' in window) {
  console.log('Selenium bot detected!')
}

In addition, when Selenium executes JavaScript in the page context, such as via driver.executeScript() or when interacting with DOM elements, it can leave further traces. One such artifact is the window.ret_nodes variable:

if(window.ret_nodes) {
  console.log('Selenium bot detected!')
}

Bot Detection: A Never-Ending Cat and Mouse Game?

Attackers don’t stop at masking the user agent or overriding navigator.webdriver. Sophisticated bots now spoof every layer of detection, canvas fingerprinting, mouse movements, timezone settings, and even IP reputation. They rotate residential proxies, automate CAPTCHA solving, and mimic real user behavior with increasing precision.

Thanks to open-source frameworks like Patchright and Selenium Driverless, attackers no longer need deep technical expertise to build evasive bots. With a few lines of code and access to low-cost CAPTCHA solvers, they can deploy large-scale credential stuffing or fake account creation campaigns that slip past legacy defenses.

That’s why detecting bots in 2025 requires more than static fingerprinting. Effective defenses combine multiple signals—browser behavior, IP reputation, proxy detection, and in-session anomalies—evaluated in real time with adaptive models. Fingerprints are still useful, but they’re just one piece of a much larger detection puzzle.

Read next