Skip to content
Cascading Labs QScrape VoidCrawl Yosoi

Stealth Mode

VoidCrawl uses a minimal-footprint stealth strategy inspired by undetected-chromedriver, zendriver, and nodriver. Stealth is enabled by default.

Philosophy: Less Is More

Most browser automation tools try to spoof every fingerprint signal — fake plugins, fake WebGL, fake user-agent strings. This backfires against modern WAFs (Akamai, Cloudflare, PerimeterX) because:

  1. Spoofed values are inconsistent. A hardcoded Chrome/131 user-agent on a system running Chromium 146 is an instant flag.

  2. The spoofing itself is detectable. Each Page.addScriptToEvaluateOnNewDocument CDP call is a fingerprint. Overriding navigator.plugins with a Proxy/getter behaves differently from the real PluginArray prototype.

  3. The automation signal isn’t in JS — it’s in Chrome’s launch flags. chromiumoxide’s default flags include --enable-automation, which tells every WAF “I’m automated” before any page loads.

VoidCrawl’s approach: don’t fake anything except the one property Chrome explicitly sets for automation (navigator.webdriver). Instead, launch Chrome with clean flags that don’t advertise automation.

What Changed from chromiumoxide Defaults

Removed (toxic flags)

FlagWhy it’s bad
--enable-automationLiterally opts in to automation detection
--disable-extensionsNormal Chrome always has extensions support
--enable-blink-features=IdleDetectionUnusual feature that fingerprints automation

Added (zendriver flags)

FlagPurpose
--disable-blink-features=AutomationControlledRemoves Chrome’s automation-controlled blink feature
--disable-features=IsolateOrigins,site-per-processDisables site isolation used for fingerprinting
--no-pingsSuppresses background pings
--disable-component-updatePrevents background update checks
--homepage=about:blankClean startup page

What the JS Stealth Layer Does

Only two patches are injected via addScriptToEvaluateOnNewDocument:

1. navigator.webdriver Removal

Chrome explicitly sets navigator.webdriver = true when connected via CDP. VoidCrawl deletes it:

delete Object.getPrototypeOf(navigator).webdriver;
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined,
configurable: true,
});

2. Force-Open Shadow DOMs

Cloudflare Turnstile and similar WAF challenges render inside closed shadow roots. VoidCrawl forces all attachShadow calls to use mode: 'open':

Element.prototype._attachShadow = Element.prototype.attachShadow;
Element.prototype.attachShadow = function(init) {
return this._attachShadow({ ...init, mode: 'open' });
};

What We Don’t Patch (and why)

SignalWhy we leave it alone
navigator.pluginsReal Chrome already populates this. Faking it creates detectable inconsistencies.
navigator.userAgentWe use Chrome’s real UA. Hardcoding a version creates a mismatch.
WebGL vendor/rendererThe real GPU info is more convincing than any fake string.
window.chrome.runtimeReal Chrome already has this.
Canvas fingerprintCan’t be spoofed reliably without introducing detectable noise.

Headful vs Headless

WAF-protected sites require headful mode

Headless Chrome has fundamental differences that sophisticated WAFs detect regardless of JS patches:

  • Different rendering pipeline (no compositing)
  • Missing screen/display properties
  • HTTP/2 TLS fingerprint differences

For Akamai, Cloudflare, and similar WAFs, use headful mode via Docker & VNC.

from voidcrawl import BrowserConfig, BrowserPool, PoolConfig
# For WAF-protected sites -- use headful
config = PoolConfig(
browser=BrowserConfig(headless=False),
)
async with BrowserPool(config) as pool:
async with pool.acquire() as tab:
await tab.navigate("https://waf-protected-site.com")
await tab.wait_for_stable_dom(timeout=15.0)
html = await tab.content()

DOM Stability Waiting

JS-heavy sites and WAF challenge pages don’t have their content ready at page load. Instead of a blind sleep(), use wait_for_stable_dom():

async with pool.acquire() as tab:
await tab.navigate(url)
stabilised = await tab.wait_for_stable_dom(
timeout=15.0, # max seconds to wait
min_length=5000, # min chars before page is "real"
stable_checks=5, # consecutive stable polls required
)
if not stabilised:
print("Page didn't fully render")

This polls document.body.innerHTML.length every 300ms and considers the page ready when the size stabilizes above the minimum threshold. It prevents redirect gates, loading spinners, and challenge pages from being mistaken for real content.

Disabling Stealth

from voidcrawl import BrowserConfig, BrowserSession
async with BrowserSession(BrowserConfig(stealth=False)) as session:
page = await session.new_page("https://trusted-site.com")

Stealth is always on for BrowserPool. The minimal patches have negligible overhead.

Real-World Results

Tested against Akamai WAF (BusinessWire) — the same site that blocks Playwright, Selenium, and chromiumoxide with heavy stealth patches:

ApproachResult
chromiumoxide defaults (--enable-automation)403 Access Denied
chromiumoxide + heavy JS spoofing + fake UA403 Access Denied
chromiumoxide + clean flags + no UA overrideSuccess (600K chars)
zendriver (reference)Success
Plain curl403 Access Denied

The lesson: the flags matter more than the JS patches.

Going Further

FAQs

Will stealth mode bypass all bot detection?

No. VoidCrawl’s stealth handles most common WAFs (Akamai, Cloudflare basic). Advanced protections that analyze traffic patterns, require CAPTCHAs, or use device attestation are outside the current scope.

Does stealth add latency?

Negligible. The JS patches are injected once per tab creation via addScriptToEvaluateOnNewDocument and execute before any page script. The flag changes are applied at Chrome launch time with zero runtime cost.

Why not use a custom user-agent?

A custom UA that doesn’t match the actual Chrome version is an instant detection signal. VoidCrawl uses Chrome’s real user-agent, which is always consistent with the actual browser binary.

References

zendriver. cdpdriver. Async Chrome automation with stealth, successor to undetected-chromedriver. https://github.com/cdpdriver/zendriver

nodriver. ultrafunkamsterdam. Predecessor to zendriver. https://github.com/ultrafunkamsterdam/nodriver

undetected-chromedriver. ultrafunkamsterdam. Patched ChromeDriver to bypass bot detection. https://github.com/ultrafunkamsterdam/undetected-chromedriver