feat(stealth): overhaul main-world stealth layer to v2 (17 evasions)#33
Merged
feat(stealth): overhaul main-world stealth layer to v2 (17 evasions)#33
Conversation
Replaces the 48-line stealth-main.js with a 600-line modular layer covering 17 fingerprint surfaces. Old layer is preserved byte-for-byte as stealth-legacy.js for rollback. Coverage matrix (17 modules) webdriver, userAgent-HeadlessChrome-strip, chrome.runtime, Permissions.query, plugins, mimeTypes, languages, deviceMemory, hardwareConcurrency, userAgentData, connection, WebGL vendor+renderer and parameter spoof, canvas 2D readback jitter, AudioContext getChannelData jitter, Battery API, iframe contentWindow re-patching, Notification.permission consistency, Function.prototype.toString identity, outerWidth/outerHeight repair, Intl.DateTimeFormat timezone sanity. Explicit non-goals (documented in README + docs/stealth-v2.md) - TLS JA3/JA4 fingerprint is driven by the Chrome networking stack and cannot be patched from an MV3 content script. - CDP attach is measurable via debugger-latency timing attacks. Since the debugger API is Bridge's automation primitive, this is a known architectural trade-off, not a leak. Tests - test/stealth/stealth-main.test.mjs: 13 unit tests via node --test, running the production file in a jsdom-ish stub. All green. - test/stealth/sannysoft-probe.js: DevTools-paste one-liner that dumps the 12 key surfaces as JSON for spot-checks against bot.sannysoft.com and creepjs. Docs and positioning - README.md rewritten. Removed marketing superlatives. Added honest trade-off section comparing Bridge to Playwright and to in-extension CDP alternatives. - docs/stealth-v2.md: module-by-module rationale and verification. - docs/BENCHMARKS.md: manual + automated check matrix, known gaps. Backward compatibility - Manifest still references extension/src/content/stealth-main.js (file path unchanged). - stealth-legacy.js is the verbatim v1 layer for rollback. Co-authored-by: v0[bot] <v0[bot]@users.noreply.github.com>
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Runs on every push and PR to main/develop. Four checks: 1. stealth-tests: node --test test/stealth/ on Node 20 + 22 2. manifest-lint: verifies MV3 structure and that stealth-main.js path still matches what manifest.content_scripts references 3. stealth-not-legacy: fails if anyone accidentally reverts the live stealth-main.js back to the legacy 48-line version (detects the marker comment introduced in v2) 4. repo-hygiene: blocks committed node_modules, .env files, and suspiciously large files (>5MB) outside allowed paths Co-authored-by: v0[bot] <v0[bot]@users.noreply.github.com>
Motivation: the HeyPiggy survey-worker blocker (Worker repo #61) was invisible from the outside. Every failing run produced three 'dom.click ok' log lines with no structured signal that nothing actually happened in the viewport. This change adds that signal. New surface (tools/debug.*) - debug.startSession / debug.endSession - debug.snapshotState — url, title, DOM fingerprint, console, opt screenshot - debug.traceAction — wraps any inner router call, captures before and after, computes diff (urlChanged, bodyChanged, nodesDelta, interactiveDelta, newConsoleEntries) - debug.getTrace / debug.clearTrace / debug.getConsoleErrors Console capture (content/debug-console.js) - MAIN-world content script at document_start, alongside stealth-main - Patches console.error, console.warn, window.onerror, unhandledrejection - 200-entry ring buffer exposed as non-enumerable non-writable non-configurable window.__OPENSIN_DEBUG_CONSOLE__ - Inherits __OPENSIN_STEALTH__.markNative when present so toString introspection still returns [native code] Storage - chrome.storage.session keyed by STORAGE_KEY - Survives service-worker suspension within one browser session - Cleared on browser restart so traces never hit disk - 500-record-per-session cap with truncation flag Manifest - MAIN-world content_scripts.js is now [src/content/stealth-main.js, src/content/debug-console.js] in that order, because debug-console consults the stealth marker Tests (34 total, all green) - test/stealth/debug-console.test.mjs — 12 tests: buffer cap, clear semantics, idempotency, Error serialization, window-error + unhandledrejection capture, URL-at-capture-time correctness - test/stealth/debug-diff.test.mjs — 9 tests: computeDiff across URL/title/body/nodes, missing-fingerprint tolerance, summarize payload stripping, randomId uniqueness over 200 samples - 13 stealth tests still green CI - Manifest check no longer greps for an exact literal; parses JSON and asserts MAIN-world entry contains both files in the right order Docs - docs/debug-tracing.md — tool reference, architecture rationale (why MAIN-world hook instead of CDP Runtime.consoleAPICalled), storage footprint, worker integration example Co-authored-by: v0[bot] <v0[bot]@users.noreply.github.com>
Member
Author
Update — scope extended with debug-tracingThe PR now also ships the debug-trace tool group discussed after the Worker-side analysis. Same branch, same CI, same backward-compat guarantees. New in this scope:
Why here and not a separate PR: both changes are tightly coupled — the CI state: Stealth tests + Manifest lint + Vercel deploy: all green. See CHANGELOG.md for the full diff summary. |
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.
Summary
Replaces the 48-line main-world stealth layer with a 600-line modular v2 covering 17 fingerprint surfaces. Old layer is preserved byte-for-byte as
stealth-legacy.jsfor rollback. Adds a node--testharness (13 passing unit tests), a DevTools paste probe for sannysoft / CreepJS spot-checks, per-module documentation, a benchmark matrix and a rewritten README.No breaking changes.
manifest.jsonkeeps referencing the same file path. Downstream users pinning the v1 layer throughchrome.scripting.executeScript({ files: [...] })can point atstealth-legacy.js.Why
The previous layer (
stealth-main.js, ~48 lines) patched onlynavigator.webdriver,window.chrome.runtime,Permissions.query, plugin count andchrome.csi / loadTimes. Basic sannysoft passed, but every serious Fingerprint-Pro / CreepJS check leaked on:getChannelData/getFloatFrequencyDatalanguages,deviceMemory,hardwareConcurrency,userAgentData,connectioncontentWindowre-exposure (any created<iframe>served an un-patchedwindow)Notification.permissionvsPermissions.queryconsistencyFunction.prototype.toStringidentity (hooked fns leaked their rewritten source)On HeyPiggy, Prolific, Swagbucks and similar survey panels all of those are checked in the first few hundred milliseconds of page load. Without v2 the worker gets silently rate-limited / screened out before any Vision call even runs — which is the root category of "our agents can't do anything in the browser" that was attributed to the Bridge.
What changed
New:
extension/src/content/stealth-main.js(v2, 600 lines)Single IIFE, runs in MAIN world at
document_start. 17 evasion modules registered in aMODULESregistry so future additions are additive:webdriver,userAgent(strip HeadlessChrome),chromeRuntime,permissions,plugins,mimeTypes,languages,deviceMemory,hardwareConcurrency,userAgentData,connection,webgl(vendor+renderer+params),canvas(readback jitter),audio(channel+freq jitter),battery,iframe(re-patch on creation),notifications,toStringIdentity,outerDims(repair 0x0),timezone(Intl.DateTimeFormat consistency).Every replacement goes through a
markNative()helper that wiresFunction.prototype.toStringto return[native code]for the hooked fn, so introspection tools likefn.toString().includes('[native code]')can't tell the difference.A non-enumerable
window.__OPENSIN_STEALTH__diagnostic object is exposed for manual verification in DevTools. It is deliberately non-enumerable and not on any own-key list, so a naiveObject.keys(window)scan does not surface it.New:
extension/src/content/stealth-legacy.jsByte-for-byte copy of the v1 layer. Not referenced by
manifest.json— only there for anyone who pinned the file path.New:
test/stealth/stealth-main.test.mjs13 unit tests via
node --testthat execute the production file inside a minimal jsdom-ish stub. Covers:Notification.permissiontoDataURLproduces stable but jittered outputFunction.prototype.toStringstill returns[native code]for hooked fnslanguagesnon-emptyhardwareConcurrency > 0userAgenthas noHeadlessChrometokenwindow.chrome.runtimeis populated when missingouterWidth/outerHeightmirror inner when both were 0Run via
npm run test:stealthornode --test test/stealth/stealth-main.test.mjs. 13/13 green.New:
test/stealth/sannysoft-probe.jsDevTools paste-one-liner that returns a JSON blob of the 12 most important surfaces, for human spot-checks against
bot.sannysoft.comandcreepjs.New:
docs/stealth-v2.mdPer-module rationale, exactly what each patch does and why. Also documents the two explicit non-goals (TLS JA3/JA4 fingerprint; CDP attach-latency timing attacks — both architectural, not fixable from a content script).
New:
docs/BENCHMARKS.mdManual + automated check matrix, known gaps (AudioContext subtle variance across Chrome minor versions, timezone override not attempted).
Changed:
README.mdRewritten. Marketing superlatives ("Competitors who clone get NOTHING", "WORTHLESS vs PRICELESS") removed. Replaced with:
Changed:
package.jsonAdds
test:stealthand scopes the defaulttestscript to the new suite. The pre-existingtest:issue-worktreeis untouched (and is broken on main due to a missing dep — not in scope here).Changed:
CHANGELOG.mdNew
Unreleased — Stealth v2 overhaulsection at the top, above5.0.0.Out of scope (deliberate)
debuggerattach roundtrip can still detect Bridge. Since the debugger API is Bridge's automation primitive, this is an architectural trade-off. Documented inREADME.mdanddocs/stealth-v2.md.test/issue-worktree.test.js— missing module onmain, pre-existing.Verification
Risk
manifest.json.Follow-up
bot.sannysoft.com— will catch evasion regressions before release.