Bot Detection Evasion in 2026: What Works and What Doesn't
Bot detection has evolved significantly. Hiding navigator.webdriver and faking a User-Agent barely scratches the surface of modern detection. Here's a breakdown of common evasion techniques and what they actually address.
Technique 1: User-Agent spoofing
Changes the navigator.userAgent string to claim a different browser or version.
await page.setUserAgent(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' +
'(KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36',
)
The problem: Detection systems don't trust the User-Agent string. They verify it against JavaScript and CSS feature support, WebGL capabilities, and platform-specific behaviors. If your UA claims Chrome 147 but your browser's feature set doesn't match that version, the inconsistency itself becomes a detection signal.
Ineffective alone. Only useful when combined with a complete, consistent set of signals.
Technique 2: Stealth plugins
puppeteer-extra-plugin-stealth, playwright-stealth, and similar tools patch known detection vectors: navigator.webdriver, navigator.plugins, chrome.runtime, Permissions.query, and WebGL renderer strings.
What they don't fix:
- Property descriptor analysis: is
navigator.webdrivertruly absent, or just patched to returnfalse? - Prototype chain validation: is
navigator.pluginsa realPluginArraywith the correct prototype? - Cross-scope consistency: do Workers see the same patched values?
Function.prototype.toStringintegrity: do patched functions still report[native code]?
A useful baseline for the most basic checks, but modern detection has moved past these signals.
Technique 3: Browser binary patching
Tools like Patchright (a Playwright fork) and undetected-chromedriver modify the browser binary itself rather than injecting JavaScript patches at runtime.
Why it's better: Binary-level patches survive iframe creation, Worker spawning, and toString() inspection because the modifications exist at the C++ level. Runtime JavaScript patches don't survive these checks.
What it doesn't fix: Binary patching removes automation artifacts, but doesn't create a consistent identity. Your fonts, GPU parameters, timezone, screen size, and API feature set still need to match a plausible real browser profile.
Technique 4: Real browser profiles
Launching with a persistent user data directory that has authentic browser state: cookies, history, extensions, cached data, stored permissions.
// Playwright example
const context = await chromium.launchPersistentContext(
'/path/to/real-chrome-profile',
{
channel: 'chrome', // Use system Chrome, not Chromium
locale: 'en-US',
timezoneId: 'America/New_York',
},
)
A real profile has authentic extension data, plugin state, and cached resources. The browser state is genuine rather than synthesized. The trade-off is operational complexity: maintaining and syncing real profiles adds work.
Technique 5: Headful mode with virtual display
Running Chrome in headed mode with xvfb (X virtual framebuffer) on Linux:
xvfb-run --auto-servernum chromium --no-sandbox
Old headless mode (Chrome's original --headless flag, before Chrome 112) had differences in font rendering, compositor behavior, and WebGL output. New headless mode (--headless=new, the default in modern Puppeteer/Playwright) uses the same rendering pipeline as headed mode, largely eliminating these differences. Using xvfb is still useful if you need to run older Chrome versions or want to ensure identical rendering behavior.
Still has automation artifacts from the control protocol (CDP/WebDriver). Best combined with binary patching.
What detection systems look at
Detection works in layers:
Layer 1: Automation flags
navigator.webdriver, window.chrome, CDP artifacts. Most bots patch these, so they're low-value signals on their own.
Layer 2: Fingerprint consistency
Cross-referencing browser signals for internal consistency. User-Agent version vs. feature support, claimed OS vs. font list, timezone vs. locale. This is where most bots fail.
Layer 3: Behavioral analysis
Mouse movements, scroll patterns, click timing, typing cadence. Analyzed server-side, outside the scope of fingerprint-level detection.
Layer 4: Network analysis
TLS fingerprint (JA3/JA4), HTTP/2 settings, TCP parameters. These reveal the actual network client regardless of JavaScript-level spoofing.
Layer 5: Reputation
IP reputation, ASN history, cookie age, account age. Historical signals that can't be fabricated in a single session.
StealthCheck focuses on layers 1 and 2, the signals visible through the browser's JavaScript environment. Layers 3 through 5 require different tools.
How to evaluate your setup
The key question isn't "which stealth plugin should I use?" but "what does my browser fingerprint look like?"
- Run a free test to see your current score
- Review the issues, each one explains what's detectable and why
- Fix the flagged issues and retest
- Retest after any Puppeteer/Playwright/Chrome update
Further reading
Test your browser stealth for free
Run a free fingerprint test and see exactly what detection systems see.