feat(browse): comprehensive anti-bot stealth patches#1112
Open
garrytan wants to merge 4 commits into
Open
Conversation
Add stealth.ts module that addresses all known automation fingerprints: 1. navigator.webdriver property deletion (not just value override) - bot detectors check property existence via 'webdriver' in navigator 2. WebGL renderer spoofing (SwiftShader → Apple M1 Pro) - SwiftShader is the #1 giveaway of container/headless environments 3. Proper PluginArray that passes instanceof checks - raw arrays fail PluginArray instanceof which DataDome/Cloudflare check 4. Complete chrome object (app, runtime, loadTimes, csi) - shallow stubs missing chrome.app get flagged 5. CDP runtime artifact cleanup (cdc_*, $cdc_*, __webdriver*) 6. Permissions API normalization (prompt, not denied) 7. Media devices presence for containers 8. Function.toString() protection - overridden functions look native Passes SannySoft (bot.sannysoft.com) 100%. Replaces inline patches in browser-manager.ts with shared module used by both headless launch() and headed launchHeaded() paths. Tested against: NYT, LinkedIn, Google, Bloomberg, BleepingComputer, Brave Search, DuckDuckGo - all previously blocked from automation browsers, all now pass through. Remaining hard targets (Reddit, FT, WSJ) blocked by IP reputation checks beyond browser fingerprinting.
E2E Evals: ✅ PASS8/8 tests passed | $1.19 total cost | 12 parallel runners
12x ubicloud-standard-2 (Docker: pre-baked toolchain + deps) | wall clock ≈ slowest suite |
added 3 commits
April 21, 2026 03:06
Tests (52 total, 0 failures): Unit tests (33): - Module exports validation (stealthArgs shape, applyStealthPatches type) - Launch args content (AutomationControlled, no-first-run, no forbidden flags) - Init script source analysis (all 10 patch vectors verified present) - applyStealthPatches API (mock context, GPU args, serialization, idempotency) - Adversarial edge cases (array spread safety, extension compat, GPU plausibility) - Import integration (browser-manager.ts correctly imports and calls both paths) - Old inline patches removal verification E2E tests (19): - Real Chromium launch with stealth patches applied - navigator.webdriver value AND property existence - WebGL1 + WebGL2 renderer spoofing - PluginArray instanceof + shape verification - Complete chrome object (app, runtime, loadTimes, csi) - Languages, permissions, CDP artifacts, Playwright globals - Platform/UA consistency - Patches survive page navigation Bug fix: navigator.platform now spoofed to 'MacIntel' when UA claims Macintosh. Previously reported 'Linux x86_64' in containers, which contradicts the Mac user agent and is a detectable fingerprint mismatch. Caught by the e2e test.
1. HIGH — Function.toString Map exfiltration: Replaced Map with WeakMap + bound methods. A malicious page could monkeypatch Map.prototype.has to capture the override store, then use it to cloak malicious functions as [native code]. WeakMap with pre-bound has/get methods prevents this side-channel. 2. MEDIUM — Static GPU fingerprint: Default GPU renderer now randomly selects from 5 common Apple chip variants (M1, M1 Pro, M1 Max, M2, M3) per session. Prevents sites from building a static GStack-specific fingerprint signature. 3. Tests updated: 54 total (35 unit + 19 e2e), 0 failures. Added tests for WeakMap usage and GPU randomization.
1. HIGH — webdriver fallback logic bug: define-then-delete on instance re-exposes prototype getter returning true. Fixed: override getter directly on Navigator.prototype when delete fails. 2. HIGH — chrome.runtime clobbered unconditionally, breaking extension messaging (content scripts, sidepanel, background). Fixed: only stub methods that don't already exist (if !w.chrome.runtime.connect ...). 3. MEDIUM — PermissionStatus shape missing EventTarget behavior. Sites calling addEventListener on the result would throw. Fixed: create object via Object.create(EventTarget.prototype). 4. MEDIUM — Plugin item()/namedItem() returned undefined instead of null for missing entries. Detectable and breaks strict checks. Fixed: ?? null. 5. MEDIUM — WebGL params spoofed even without debug extension, which is detectable as synthetic. Fixed: check getExtension() first. 6. LOW/MEDIUM — Only toString itself was registered in the WeakMap; patched getParameter was still inspectable. Fixed: register all patched prototype functions. 7. LOW — Import from playwright-core instead of playwright (transitive dependency). Fixed: import from playwright (direct dependency). All 129 tests pass (54 stealth + 75 existing).
shelman09
pushed a commit
to Namleh-Studios/gstack
that referenced
this pull request
May 19, 2026
…tches (garrytan#1112) Rebases @garrytan's PR garrytan#1112 (Apr 2026, abandoned) onto the current browse/src/stealth.ts contract. The existing minimal "codex narrowed" stealth (webdriver-mask + AutomationControlled launch arg) stays the default. PR garrytan#1112's six additional patches are added behind an opt-in GSTACK_STEALTH=extended env flag. Extended-mode patches (applied AFTER the default mask, in order): 1. delete navigator.webdriver from prototype (not just the getter — detectors check `"webdriver" in navigator`) 2. WebGL renderer spoof to Apple M1 Pro (SwiftShader was the garrytan#1 software-GPU tell in containers) 3. navigator.plugins returns a PluginArray-prototype-passing array with MimeType objects and namedItem() 4. window.chrome populated with chrome.app, chrome.runtime, chrome.loadTimes(), chrome.csi() with realistic shapes 5. navigator.mediaDevices backfilled when headless drops it 6. CDP cdc_*-prefixed window globals cleared Why opt-in: the default mode's contract is fingerprint CONSISTENCY, which protects against detectors that flag spoofing mismatch. Extended mode actively lies about the environment; sites that reflect on these properties can break. Users who hit detection in default mode can flip GSTACK_STEALTH=extended for SannySoft 100% pass-rate. Twenty unit tests pin the env-flag semantics, all six patches' code presence, and the applyStealth wiring order. Live SannySoft pass-rate verification stays in the periodic-tier E2E suite. Contributed by @garrytan via garrytan#1112 (rebased — original PR opened before the codex-narrowed minimum landed; rebase preserves the narrowed default while adding the SannySoft-passing path as opt-in). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
New
stealth.tsmodule with comprehensive anti-bot detection countermeasures for GStack Browser. Replaces the inline patches inbrowser-manager.tswith a shared module used by both headless and headed launch paths.Why
The existing stealth patches missed several critical detection vectors:
webdriverin navigatortrue(property exists)false(deleted from prototype)SwiftShaderApple M1 Pro, OpenGL 4.1chrome.appBefore: SannySoft flagged 3 vectors. After: 100% pass rate.
Detection Vectors Addressed
navigator.webdriverproperty existence — not just the value, the property itself must be deleted fromNavigator.prototype. Bot detectors check"webdriver" in navigator.PluginArray— real Chrome plugins withinstanceof PluginArraypassing,MimeTypeobjects,namedItem()method.chromeobject —chrome.app,chrome.runtime,chrome.loadTimes(),chrome.csi()with correct shapes.cdc_*,$cdc_*,__webdriver*properties injected by Chrome DevTools Protocol.promptfor notifications (automation browsers returndenied).navigator.mediaDevicesin environments that lack them.Function.toString()protection — overridden functions return[native code]to avoid detection.Changes
browse/src/stealth.ts— shared stealth module withstealthArgsandapplyStealthPatches()browse/src/browser-manager.ts— imports and uses new module in bothlaunch()andlaunchHeaded(), removing 56 lines of inline patchesTested Against
✅ NYT, LinkedIn, Google Search, Bloomberg, BleepingComputer, Brave Search, DuckDuckGo
Remaining hard targets (Reddit, FT, WSJ) are blocked by IP reputation checks beyond browser fingerprinting — not a fingerprint issue.
How to Test
bun run server→ launch browserbot.sannysoft.com→ all tests should passnytimes.com→ should load without CAPTCHAlinkedin.com/in/garrytan→ should load profile (not login wall)