Anti-Bot

What Is navigator.webdriver Detection?

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. It is defined by the W3C WebDriver spec and set by Selenium, Playwright, Puppeteer, and any CDP-driven browser 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 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; 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:

  • The getter is now a JavaScript function. Object.getOwnPropertyDescriptor(navigator, "webdriver").get.toString() returns the patch source instead of "[native code]" - caught by Function.toString() inspection.
  • On real Chrome, webdriver lives on Navigator.prototype, 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, canvas/WebGL/audio coherence, behaviour, and the harder probes.

This is why tooling that only hides navigator.webdriver (some minimal stealth 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.

Related terms

What Is Headless Browser Detection?
Headless browser detection is the set of probes anti-bot systems use to distinguish a headless or instrumented Chrome session from a real us…
What Is Function.toString() Inspection?
Function.prototype.toString() inspection is the technique anti-bot scripts use to detect runtime JavaScript patches. Every JS function expos…
What Is Browser Fingerprinting Evasion?
Browser fingerprinting evasion is the practice of configuring an automated browser so that the combined fingerprint it presents — canvas, We…
What Is the Chrome DevTools Protocol (CDP)?
The Chrome DevTools Protocol (CDP) is the low-level interface for instrumenting and controlling Chromium-based browsers. Puppeteer, Playwrig…
What Is Anti-Bot Detection?
Anti-bot detection is the set of techniques websites use to distinguish automated traffic from human users — and to block, challenge, or thr…
What Is CDP Detection?
CDP detection is the family of techniques anti-bot scripts use to tell that a browser is being driven through the Chrome DevTools Protocol (…
What Is Browser Fingerprinting?
Browser fingerprinting is a technique that identifies and tracks a visitor by combining dozens of small, observable characteristics of their…
Anti-Bot Vendor Detection Cheatsheet
The first step of any scrape against a protected site is identifying which anti-bot vendor is in front of it. The vendor determines almost e…
How Do Websites Detect Web Scrapers?
Websites detect scrapers by collecting hundreds of signals across the network, transport, browser, and behavioral layers, then scoring the c…

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 hiding navigator.webdriver make my scraper undetectable?

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. Hiding the flag is the floor of stealth, 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], 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-30