Skip to content
Anti-Bot

What Is navigator.webdriver Detection?

Pim

Pim · Scrappey Research

May 31, 2026 4 min read

What Is navigator.webdriver Detection? — conceptual illustration
On this page

navigator.webdriver is a standardized boolean that returns true when the browser is being controlled by automation. Think of it as a built-in honesty flag: any browser driven by software, instead of a person, is supposed to admit it. It is defined by the W3C WebDriver spec and set by Selenium, Playwright, Puppeteer, and any CDP-driven browser (one controlled through Chrome's DevTools Protocol) launched in automation mode. Because reading it is a single property access, it is the very first and cheapest check almost every anti-bot script runs. Hiding it is table stakes - necessary to not fail instantly, but far from sufficient, because dozens of harder signals remain.

Quick facts

SpecW3C WebDriver - navigator.webdriver is true under automation
Set bySelenium, Playwright, Puppeteer, ChromeDriver, any --enable-automation launch
Cost to checkOne property read - the cheapest bot signal that exists
Spoof tellA JS-defined getter fails Function.toString() inspection
RealityHiding it is necessary but never sufficient

Where the flag comes from

The WebDriver standard - the W3C rulebook for how software remote-controls a browser - requires a conforming automation session to set navigator.webdriver to true, so that pages can tell they are being driven. Chrome sets it when launched with the --enable-automation switch or controlled via the DevTools Protocol; Firefox sets it under Marionette (Firefox's automation engine); every mainstream automation framework triggers it by default. On a normal human browser session it is false.

That makes it a perfect first-pass filter: one line, if (navigator.webdriver) flag(), catches every scraper that has not specifically dealt with it - which is a surprising number, because many tutorials never mention it.

Why naive hiding gets caught anyway

The obvious fix is to overwrite the property: Object.defineProperty(navigator, "webdriver", { get: () => false }). This makes the value read false, but it introduces new, detectable artifacts (telltale leftovers of the tampering):

  • The getter - the small function that runs when the value is read - is now ordinary JavaScript. Object.getOwnPropertyDescriptor(navigator, "webdriver").get.toString() returns the patch source instead of "[native code]" (the marker browsers show for built-in functions) - caught by Function.toString() inspection.
  • On real Chrome, webdriver lives on Navigator.prototype (the shared blueprint all navigator objects inherit from), not as an own-property of the navigator instance. Defining it on the instance changes where it appears in the prototype chain - itself a tell.
  • If the patch runs after page scripts, there is a race where the real value is briefly observable.

So spoofing the value with JavaScript trades one obvious signal for a subtler one. The clean fix is to launch the browser so the flag is never set - excluding the enable-automation switch - or to patch at the engine level so the property reads false natively.

Necessary but not sufficient

The most important thing to understand about navigator.webdriver is its place in the detection stack: it is the floor, not the ceiling. Passing it means you are not in the bottom tier of trivially-detectable bots. It tells a vendor nothing reassuring - real users pass it too - so failing it is fatal but passing it earns you nothing. Serious anti-bot systems (Kasada, DataDome, Akamai) treat it as a checkbox and move on to TLS fingerprints (clues from the https handshake), canvas/WebGL/audio coherence (whether those graphics and sound APIs all describe the same machine), behaviour, and the harder probes.

This is why tooling that only hides navigator.webdriver (some minimal patching scripts) still gets blocked everywhere that matters. The flag is the first gate; the real contest is the coherent fingerprint behind it. Engine-level browsers launch with the flag genuinely absent and harden the rest.

Code example

javascript
// The cheapest bot check in existence
if (navigator.webdriver) {
  // Block-grade for the bottom tier of scrapers.
}

// Naive hide - works on the VALUE but adds a new tell
Object.defineProperty(navigator, 'webdriver', { get: () => false });

// How a vendor catches the naive hide:
const d = Object.getOwnPropertyDescriptor(navigator, 'webdriver');
const patched = d && d.get &&
  !Function.prototype.toString.call(d.get).includes('[native code]');
// patched === true  ->  the getter is JS, not native  ->  flagged

// Real fix: launch without the automation switch so the flag is never set,
// e.g. Puppeteer: ignoreDefaultArgs: ['--enable-automation'],
// or use an engine that reports webdriver=false natively.

Next in How automation gets detected · 4 of 6

Drivers leave more behind than one flag.

What Is CDP Detection?

Related terms

Concept map

Concept map

How navigator.webdriver 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 the navigator.webdriver flag tell an anti-bot system everything?

No. It clears the single cheapest check, which is necessary, but real users pass it too, so passing earns no trust. Every serious anti-bot system then evaluates TLS fingerprints, canvas/WebGL/audio coherence, behaviour, and harder probes. The flag is the floor of what detection looks at, not the ceiling.

Why does Object.defineProperty on navigator.webdriver still get caught?

Because the replacement getter is a JavaScript function whose toString() returns the patch source instead of [native code] (the marker for built-in functions), and because the property ends up on the navigator instance rather than on Navigator.prototype where it lives in real Chrome. Both are detectable. Launching without the automation switch avoids creating the flag at all.

How do I launch a browser so navigator.webdriver is false from the start?

Exclude the automation switch - for example Puppeteer with ignoreDefaultArgs: ["--enable-automation"], or use a build/engine that does not set the flag. Engine-level anti-detect browsers report it false natively, so there is no JS getter to inspect.

Last updated: 2026-05-31