First of all, thanks for the support to anyone form this subreddit, didnt realize hound was gonna get this much traffic in like a day, now this forces me to improve it even more, so here i am:
Most MCP web tools hit a wall at Cloudflare. They either use a basic HTTP client that gets 403'd, or a headless browser that leaks detection signals.
I've been working on Hound's stealth fetch pipeline and just shipped the result. Posting here because the approach might be useful to others building browser automation, and I want real-world feedback.
The problem
Patchright (the anti-detect fork of Playwright) handles CDP protocol leaks, but it doesn't touch the JS layer. Detectors check signals patchright never touches: HeadlessChrome in the UA string, navigator.webdriver, canvas fingerprints, WebGL vendor, plugin count, behavioral telemetry. On top of that, Cloudflare v9 uses ML behavioral scoring on mouse movement, not just JS fingerprints.
I ran a 31-target benchmark before starting: patchright + channel=chrome passed 25/31. The hardest targets (Cloudflare Turnstile, DataDome) failed.
What changed
1. System Chrome instead of bundled Chromium
The biggest single win. Use channel=chrome to launch the user's installed Google Chrome instead of the bundled Chromium. Real TLS fingerprint (JA4) that matches real Chrome traffic. Bundled Chromium's TLS fingerprint differs and is detectable. Falls back to Chromium if Chrome isn't installed.
2. JS-layer patches via commit + evaluate
This was the hardest part. Three injection methods are broken with patchright:
context.add_init_script() uses Playwright Routes, which intercept DNS resolution and break name resolution
- CDP
Page.addScriptToEvaluateOnNewDocument requires Runtime.enable, which patchright patches out (that's its whole thing)
route.fulfill() with inline <script> tags doesn't execute
The working method: page.goto(url, wait_until='commit') + immediate page.evaluate(stealth_script). The commit event fires when the response body starts arriving but before the page's own JS runs. The evaluate call injects the patches in patchright's isolated execution context before the page can read the original values.
Patches applied:
- HeadlessChrome removed from
navigator.userAgent (read real Chrome version, construct proper UA)
navigator.webdriver set to undefined (patchright sets false, which is itself a signal)
- Canvas noise: intercept
getImageData AND toDataURL with a seeded PRNG adding per-session deterministic noise to first 16 pixels. CreepJS and sannysoft compute canvas hashes via getImageData directly, so only patching toDataURL does nothing.
- Permissions API consistency
- WebGL vendor/renderer, plugins,
window.chrome, hardwareConcurrency, deviceMemory (only for bundled Chromium; system Chrome already has correct values, overriding them creates contradictions detectors cross-check for)
3. Coherent fingerprint profiles
4 internally consistent identities: Win32 + NVIDIA/Intel/AMD WebGL, MacIntel + Apple. Platform matches WebGL renderer matches GPU. Detectors cross-reference these against each other; a mismatch (Win32 platform with Apple GPU) is an instant flag.
4. Human behavior simulation
Cloudflare v9 ML model weights behavioral telemetry. Real human mouse movement isn't linear. I implemented quadratic Bezier curves with 15-30 steps, ease-in-out timing, overshoot + correction wobble. One bezier mouse move, one smooth scroll, 1-2.5s randomized dwell time. Total ~1.5-2.5s overhead, only on stealthy + humanize fetches.
The CF Turnstile solver moves the mouse to the checkbox via Bezier curve before clicking. This was the difference between passing and failing CanadianInsider (hardest Turnstile target in the benchmark).
5. Memory optimization
Browser processes leak RAM across sequential fetches. Three changes:
--renderer-process-limit=1 (we fetch one page at a time, saves ~100-200MB per avoided process)
--js-flags=--max-old-space-size=512 (caps V8 heap at 512MB vs default 4GB)
Memory.simulatePressureNotification via CDP after each fetch. This triggers Chrome's internal GC + cache drop across all processes. ~5ms, non-disruptive.
Also fixed a CDP session leak: sessions were created for memory pressure but never detached. Now detached after use.
Results
Detection test sites (bot.sannysoft.com, CreepJS, BrowserScan, Pixelscan): all checks pass.
Anti-bot protected sites:
- CanadianInsider (CF Turnstile, hardest target): 200 OK, 78KB content
- Medium (CF interstitial): 200 OK, 93KB
- StackOverflow (CF): 200 OK, 1.1MB
- NowSecure (CF challenge): 200 OK, 180KB
- Glassdoor (DataDome): 200 OK, 849KB
Google Search still returns 429. It uses its own detection independent of Cloudflare, expected.
Memory: RSS decreased by 3.5MB over 5 sequential fetches. No creep.
What I want to know
If you're using Hound with OpenCode, update and try it against the sites you actually hit:
hound -u
Specifically interested in:
- Sites where the stealthy browser still gets blocked (what protection are they running?)
- Performance on lower-spec machines (the human behavior simulation adds ~2s per stealthy fetch)
- Whether the memory optimization actually holds up over long sessions with many fetches
- Any sites where canvas noise causes rendering issues in the extracted content
GitHub: https://github.com/dondai1234/master-fetch
The full stealth benchmark is in the README.