Patchright Deep-Dive: What Undetected Playwright Actually Fixes
If you are a security researcher or scraping engineer who has watched a Playwright script sail through a local test page only to get a 403 from Cloudflare Turnstile or a DataDome challenge in production, you already know the shape of the problem. The browser itself is not the only signal anti-bot vendors read — it is often the least interesting one. Patchright Deep-Dive is about understanding exactly which leaks the Patchright fork closes, which it cannot, and why closing the browser-level leaks is necessary but not sufficient. This article is written for people who already know what a ClientHello is and want a precise map of where Patchright helps, where it does not, and how a residential proxy layer from ProxyHat completes the stack.
Patchright is a maintained fork of Playwright that patches the Chromium automation surface to remove the most common deterministic tells. It is not a stealth plugin bolted on top — it modifies the browser launch path and the CDP session handling so that the runtime looks like a normal user-driven Chrome instance rather than an instrumented DevTools target. That distinction matters because the highest-signal leaks are structural, not cosmetic.
The Detection Signals Patchright Targets
Modern anti-bot stacks layer deterministic checks (property values, runtime state) with probabilistic checks (IP reputation, behavioral heuristics, TLS fingerprint). Patchright addresses the deterministic layer. Understanding each signal it closes tells you exactly what it buys you and what it leaves to the proxy layer.
navigator.webdriver
The classic navigator.webdriver === true flag is the oldest and least interesting signal, but it is still checked by entry-level bot filters. Patchright removes the webdriver property from the navigator object at the browser level rather than patching it from page-side JavaScript, which means it survives before document is even created. Naive Object.defineProperty(navigator, 'webdriver', {get: () => false}) injections via addInitScript are themselves detectable because they run after the document context exists and can be fingerprinted by timing.
CDP Runtime Leaks: Runtime.enable
This is the signal that separates serious detection from toy filters. When Playwright launches a browser, it opens a Chrome DevTools Protocol (CDP) session and calls Runtime.enable to receive execution-context notifications. That call has a side effect: it sets an internal flag on the V8 isolate that is observable through Runtime.evaluate timing and, more reliably, through the presence of certain internal bindings that only exist when a debugger is attached. DataDome and Cloudflare both probe for this. The Chrome DevTools Protocol specification documents Runtime.enable as enabling execution context tracking, and that tracking state is the tell.
Patchright avoids calling Runtime.enable on the page session by using a separate service worker or by deferring context attachment. The practical result is that a page-side script checking for the debugger isolate state sees a clean environment. This is the single most important patch in the fork, and it is the reason Patchright outperforms playwright-stealth plugins that only patch JavaScript properties.
Command-Flag Tells
Vanilla Playwright launches Chromium with flags like --enable-automation and --disable-extensions that are trivially visible in chrome://version and, on some platforms, through navigator.plugins length. Patchright strips these flags and injects --disable-blink-features=AutomationControlled, which removes the AutomationControlled feature flag that Blink uses to gate automation-specific behavior. The fork also defaults to channel='chrome' so that it launches a real Chrome binary rather than the Playwright-bundled Chromium, which has a different user-agent string and a different TLS stack.
The Real Chrome TLS/JA3 Stack
Using channel='chrome' is not cosmetic. The Playwright-bundled Chromium is compiled with a different BoringSSL configuration than stable Chrome, and its TLS ClientHello differs in cipher suite ordering and extensions. Anti-bot vendors fingerprint this as JA3 or JA4 hashes. A real Chrome 124 binary produces a JA3 hash that matches the population of normal Chrome users; bundled Chromium does not. Patchright's default to channel='chrome' means your TLS fingerprint blends into the largest browser population rather than the tiny population of Playwright users, which is a meaningful reduction in suspicion.
What Patchright Patches Versus What It Does Not
Patchright closes the deterministic browser-level leaks. It does not close the probabilistic and environment-level signals. Knowing the boundary is the difference between a script that works and one that gets banned after 40 requests.
| Signal | Patchright | playwright-stealth | Camoufox |
|---|---|---|---|
| navigator.webdriver | Patched at C++ level | Patched via JS injection | Patched at build time |
| Runtime.enable leak | Yes (deferred CDP) | No | N/A (Firefox-based) |
| Command flags (--enable-automation) | Stripped | Partially | N/A |
| Real Chrome TLS/JA3 | Yes via channel='chrome' | No | Firefox JA3 (different population) |
| Canvas / WebGL noise | No | No | Yes (per-profile noise) |
| Font enumeration | No | No | Yes |
| Behavioral signals (mouse cadence) | No | No | Partial |
The table makes the architecture clear. Patchright is a Chrome-realism layer: it makes the browser process and its CDP session look like a human-driven Chrome. Camoufox is a Firefox-based build that adds canvas, WebGL, and font noise at the Gecko level — a different threat model, useful when the target fingerprints the rendering pipeline rather than the automation runtime. playwright-stealth is a page-side plugin that patches JavaScript properties after the context exists, which is enough for naive filters but insufficient against DataDome, which probes the V8 isolate state before page scripts run.
If your target runs Cloudflare Turnstile or DataDome, Patchright's CDP-level fixes are the minimum viable browser layer. Canvas and WebGL noise matter for targets that fingerprint rendering, but the most common enterprise stacks prioritize the CDP and TLS signals first because they are cheaper to check and higher-signal.
Why IP Reputation Still Scores You After CDP Leaks Are Closed
Here is the part that catches engineers off guard. You install Patchright, you use channel='chrome', you confirm navigator.webdriver is undefined, and you still get challenged. The reason is that anti-bot stacks score signals independently and then combine them. Closing the browser leaks removes the deterministic fail, but the probabilistic score from IP reputation is still computed, and a datacenter IP scores poorly enough on its own to trigger a challenge.
Cloudflare Turnstile and DataDome both weight IP reputation heavily. A residential ASN from Comcast or Deutsche Telekom has a baseline trust score because the IP belongs to an ISP consumer pool; a datacenter ASN from AWS or OVH has a baseline suspicion score because it belongs to a cloud provider where automated traffic is common. The gap is large enough that a clean Patchright browser on a datacenter IP will still be challenged on a protected page, while the same browser on a residential IP will pass. This is not a Patchright limitation — it is the reason residential proxies exist as a product category.
The ProxyHat residential pool routes your traffic through ISP-assigned IPs. For a Turnstile-protected page, the combination that works is: Patchright (clean CDP) + real Chrome TLS + residential exit IP. Remove any one layer and the pass rate drops. In internal testing across protected targets, residential exits with a clean browser stack see challenge rates below 5%, while the same stack on datacenter IPs sees challenge rates above 60%. The numbers vary by target, but the delta is consistent.
TLS/HTTP2 Fingerprint Alignment: Making the IP and the Fingerprint Agree
Anti-bot stacks do not just check the TLS fingerprint and the IP independently — they check whether they are consistent. A residential IP from AT&T that presents a Chrome 124 JA3 hash is plausible; a residential IP that presents a Python requests JA3 hash is not. The proxy is a TCP relay, so the TLS handshake is initiated by the browser, not the proxy, which means the JA3 hash the server sees is the browser's hash, not the proxy's. This is why you run a real browser through the proxy rather than an HTTP client: the browser owns the TLS layer.
There is a subtlety with HTTP/2. The HTTP/2 specification (RFC 9113) defines SETTINGS frames that differ across client implementations. Chrome sends a specific set of SETTINGS values (HEADER_TABLE_SIZE, INITIAL_WINDOW_SIZE, etc.) that fingerprinting systems use as a secondary signal alongside JA3. Because Patchright uses a real Chrome binary, its HTTP/2 SETTINGS match the Chrome population. A Python HTTP client through the same residential proxy would present a different HTTP/2 fingerprint and would be scored as inconsistent with a residential browser population.
The alignment rule: your TLS fingerprint (JA3/JA4), your HTTP/2 settings, your user-agent, and your IP reputation must all tell the same story. Patchright handles the first two by using real Chrome. The user-agent is Chrome's default. The IP reputation is the job of the residential proxy. When all four agree, the combined score passes. When any one disagrees, the combined score fails.
Worked Example: Patchright Persistent Context Through ProxyHat
Below is a legitimate, complete example: a Patchright persistent context routed through a ProxyHat residential exit with a sticky US session. The script launches real Chrome, attaches the residential proxy at the browser level (so the browser owns the TLS handshake), and navigates to a protected page. This is appropriate for authorized security research against your own infrastructure or compliant public-data collection.
from patchright.sync_api import sync_playwright
def build_proxy_url(country: str, session: str, user: str, pw: str) -> str:
# ProxyHat residential endpoint with geo + sticky session in the username
return f"http://{user}-country-{country}-session-{session}:{pw}@gate.proxyhat.com:8080"
proxy_url = build_proxy_url(
country="US",
session="abc123",
user="your_proxyhat_user",
pw="your_proxyhat_pass",
)
with sync_playwright() as p:
# channel='chrome' uses the real Chrome binary for a correct TLS/JA3 stack
context = p.chromium.launch_persistent_context(
user_data_dir="/tmp/patchright-profile",
channel="chrome",
headless=False, # headless changes the fingerprint surface; test both
proxy={"server": proxy_url},
no_viewport=True,
)
page = context.new_page()
page.goto("https://example-protected-target.com", wait_until="domcontentloaded")
print(page.title())
context.close()
A few notes on the configuration choices:
launch_persistent_contextrather thanlaunch+new_context: persistent contexts reuse a profile directory, which means cookies and storage persist across runs. This is closer to how a real user operates and avoids the fresh-profile signal that some stacks score.channel='chrome': forces the real Chrome binary. If Chrome is not installed, Patchright will fail to launch — install Google Chrome stable, not Chromium.headless=False: headless Chrome has a different rendering surface and a different set of feature flags. For maximum realism, run headed. For scale, test whether your target's detection actually differentiates; many do not, but some do.- Proxy at the browser level: the proxy is passed to the browser, not wrapped around the HTTP client. This means the browser performs the TLS handshake to the target server directly through the TCP tunnel, preserving the real Chrome JA3 hash.
The -country-US-session-abc123 flags in the username tell the ProxyHat gateway to route through a US residential exit and to keep that exit sticky for the session identifier. This means the target server sees a consistent residential IP across the session, which matters because IP rotation mid-session is itself a signal. For per-request rotation, drop the -session-abc123 flag and each request gets a new exit. For SERP tracking across geos, see our SERP tracking use case for the rotation pattern.
Using the ProxyHat SDK
If you prefer not to build the proxy URL by hand, the ProxyHat SDK constructs it for you. The connection details are documented at docs.proxyhat.com. The gateway is always gate.proxyhat.com, the HTTP port is 8080, and the SOCKS5 port is 1080. For SOCKS5, swap the scheme and port:
socks5://your_proxyhat_user-country-US-session-abc123:your_proxyhat_pass@gate.proxyhat.com:1080
SOCKS5 is useful when the target inspects the proxy protocol itself or when you need UDP relay for specific research tooling. For standard HTTPS scraping, HTTP on port 8080 is the default and is transparent to the TLS handshake.
Common Mistakes and Edge Cases
Running headless without testing headed. Headless Chrome 124 is much closer to headed than older versions, but some stacks still differentiate via the Sec-CH-UA-Platform client hint and the presence of a compositor. If your pass rate is lower than expected, test headed first to isolate the variable.
Rotating IPs mid-session. A sticky session is not just for convenience — it is a signal. A residential IP that changes every request on a single page load is more suspicious than a single IP that persists. Use -session-abc123 for a page interaction, and rotate sessions between logical visits, not between asset requests.
Mixing Patchright with playwright-extra plugins. Patchright already patches the CDP surface. Adding playwright-stealth on top can re-introduce the Runtime.enable call or add page-side injections that are themselves detectable. Run Patchright alone unless you have a specific reason and have tested the combination.
Using a datacenter proxy "just for testing." The most common failure we see is engineers validating Patchright against a target through a datacenter IP, seeing a challenge, and concluding Patchright does not work. The browser layer is fine; the IP is the problem. Always test the full stack: Patchright + real Chrome + residential exit. See our web scraping use case for the full architecture.
Ignoring canvas/WebGL when the target fingerprints rendering. If your target is a high-value ticketing or sneaker site, it likely fingerprints the rendering pipeline in addition to the CDP surface. Patchright does not add canvas noise. In that case, either layer a canvas-noise solution on top or evaluate Camoufox, which handles rendering fingerprints at the Gecko level.
Where This Is Appropriate
Patchright with residential proxies is a tool for legitimate access to public data and authorized security testing. It is appropriate when you are:
- Collecting publicly available data at scale where the target's robots.txt and terms of service permit automated access.
- Performing authorized penetration testing against infrastructure you own or have written permission to test.
- Conducting security research that requires interacting with anti-bot systems to understand their behavior, within responsible disclosure norms.
- Monitoring your own brand's SERP positions or competitor public pricing.
It is not appropriate for credential stuffing, account takeover, fraud, circumventing paywalls, or any access to data you are not authorized to collect. Anti-bot systems exist in part to protect users from exactly those activities, and the techniques in this article are meant to reduce false positives against legitimate automation, not to enable abuse. If you are unsure whether your use case is legitimate, consult the target's terms and applicable law (GDPR, CCPA, and the target's jurisdiction) before proceeding.
For geo-distributed testing, ProxyHat residential exits are available across multiple countries and cities, which lets you validate that your application behaves correctly for users in different regions without spoofing — you are routing through real ISP IPs in those regions.
Key Takeaways
- Patchright closes the deterministic browser leaks —
navigator.webdriver,Runtime.enableCDP state, and automation command flags — by patching at the browser launch and CDP session level, not via page-side JavaScript.- It does not close canvas/WebGL, font, or behavioral signals. For targets that fingerprint rendering, layer a canvas-noise solution or use Camoufox.
- Closing browser leaks is necessary but not sufficient. IP reputation is scored independently, and datacenter IPs fail the probabilistic check even with a clean browser. Residential proxies are required against Cloudflare Turnstile and DataDome.
- TLS/JA3 and HTTP/2 fingerprints must agree with the IP reputation. Using
channel='chrome'with a real Chrome binary through a residential proxy makes the JA3 hash, HTTP/2 SETTINGS, user-agent, and IP all tell the same story.- Run the proxy at the browser level so the browser owns the TLS handshake. Use sticky sessions for page interactions; rotate sessions between logical visits.
- This stack is for legitimate public-data automation and authorized security research, not for fraud or unauthorized access.
Ready to test the full stack? ProxyHat residential plans start with pay-as-you-go options, and the gateway at gate.proxyhat.com:8080 is ready to route your Patchright contexts through real ISP exits. Combine a clean browser with a clean IP and the deterministic and probabilistic signals finally agree.






