feat: stealth-browser detection (F1) (#11)#27
Merged
Conversation
Adds the node 0.4.0 headline capability the plugin was missing: detecting
scrapers that patch native browser functions to hide automation
(puppeteer-extra-stealth, botasaurus) — precisely the class that evades the
scanner's existing fingerprint checks.
- webdecoy-scanner.js detectLies(): probes for patched native functions via
the [native code] toString tell. Split by false-positive risk:
strong (near-zero FP): patched Function.prototype.toString, modified
navigator.webdriver getter — automation signatures.
weak (privacy extensions can trigger): permissions.query, toDataURL,
WebGLRenderingContext.getParameter, mediaDevices.enumerateDevices,
Notification.requestPermission.
Scored on the 0-100 scale: 1 strong=70, 2 strong=82 (blocks alone); a lone
weak tell scores 0; multiple weak cap at 40 (never auto-block). Lie flags
are forwarded for attribution.
- WebDecoy_Behavioral_Scorer: new stealth category applied as a max()
override (not a re-weight) so it's decisive when present but preserves the
existing PoW-verify model otherwise. Mirrors node's curve (0.7 base, +0.12
per additional strong tell, cap 0.97).
FP-safety is explicit in tests: stock Chrome/Safari/Firefox and single-weak
(privacy-extension) sessions never reach a blocking score. 41 assertions
green; JS scoring math cross-checked.
Closes #11. Part of #16.
Co-authored-by: Claude <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.
Adds node 0.4.0's headline capability the plugin was missing: stealth-browser detection. The existing scanner has solid coverage (webdriver, headless, WebRTC, canvas/WebGL) but no lie detection — which is exactly the class of scraper (puppeteer-extra-stealth, botasaurus) purpose-built to defeat those signals.
How it works
A real, unmodified browser never rewrites its own native functions. Stealth tooling patches them to spoof away tells — but the patch is itself detectable: a patched function's
toString()no longer returns"[native code]".detectLies()probes for this, split by false-positive risk:Function.prototype.toString(stealth plugins do this to hide their other patches) and a modifiednavigator.webdrivergetter. These are automation signatures.permissions.query,HTMLCanvasElement.toDataURL,WebGLRenderingContext.getParameter,mediaDevices.enumerateDevices,Notification.requestPermission.Scoring (aligned with node, tuned for zero FP)
WebDecoy_Behavioral_Scorer: new stealth category applied as amax()override (not a re-weight), so a confirmed tell is decisive — mirroring node weighting stealth highest (0.30) — while leaving the existing PoW-verify model unchanged when no lie data is present. Curve matches node:0.7 + 0.12·(strong−1), capped0.97.The DoD's hard requirement: zero new false positives
Tested explicitly. Stock Chrome/Safari/Firefox patch nothing → score 0. A privacy-extension user who trips a single weak tell → score 0, never blocked. This is the cautionary lesson from the 1.3.8
no_pluginsremoval and node dropping its FP-prone Playwright heuristics — encoded as assertions here.Tests
tests/StealthScorerTest.php(6 tests): the strong/weak curve, decisivemax()override, and FP-safety for no-lies / single-weak / multiple-weak. Full PHP suite 41 assertions, green. The scanner's JS scoring math was cross-checked with a standalone harness. (detectLies()itself is a DOM probe, exercised in a real browser rather than headlessly.)Closes #11. Part of #16.