Status#
Accepted
Context#
Python SeleniumBase provides multiple undetected/stealth modes. The Rust port must support a similar capability without relying on Python-specific patches.
Decision#
Implement a layered stealth stack:
- Binary patching (
src/stealth/patcher.rs) removes$cdc_and other automation markers from the chromedriver executable (ChromedriverPatcher).ChromeBinaryPatcherdoes the same kind of work on a cached copy of the Chrome binary, for native spoofing (see 7). - Launch arguments and preferences (
StealthOptions) disable automation indicators and harden the browser profile. - CDP overrides (
src/stealth/evasions.rs) build, from aFingerprint, the parameters ofNetwork.setUserAgentOverride,Emulation.setDeviceMetricsOverrideandEmulation.setLocaleOverride(and the timezone, geolocation and similar overrides) insafe_cdp_overrides(cdp_overridesis deprecated: it also builds the proxy credential header).BrowserSession::apply_fingerprintsends them at session start, together with the session script registered throughPage.addScriptToEvaluateOnNewDocument. The pure CDP engine (sb_cdp::identity) sends the screen of a desktop persona asEmulation.setDeviceMetricsOverridewith a0by0viewport plusscreenWidthandscreenHeight, so the browser reports the screen itself and the page keeps the viewport its window has. - JavaScript providers (
src/stealth/providers/) patch runtime fingerprints. Each of the built-in providers (default_registry()holds 29) covers one dimension: navigator, screen, WebGL, canvas and audio noise, fonts, media devices, WebRTC, plugins, timezone and more. Providers patch prototypes rather than instances and start their snippet withSB_PREAMBLE, whose helpers make a replaced function report a native name, length and[native code]source.bootstrap_scriptand the Playwright session rundefault_registry(). The WebDriver sessions in UC and CDP mode and the pure CDP engine runsession_registry()(evasions::session_script), which is the default withoutpluginsandconnection(27 providers): a real Chrome already hasnavigator.plugins,mimeTypes, the PDF viewer flag andnavigator.connectionand answers for them itself, so a replaced getter would be one more thing a page can find that a real Chrome does not have.navigator_propsno longer setspdfViewerEnabled, andclient_hintsdefinesnavigator.userAgentDataonly in a secure context, which is the only place Chrome has it. - One setting, four states. Every setting of a
Fingerprintis oneSpoof<T>:Natural(the browser’s own value, the default),Random(a coherent value drawn when the fingerprint is built),Value(T)(a plain value converts to it) orOff(the API is unavailable or denied, for the settings where that means something). The value and the intent are one field, so they cannot disagree, and a built fingerprint holds noRandomand is never mutated (to_builder()starts the next one).Fingerprint::is_masked(Dimension)is the one predicate the providers, the launch arguments, the protocol overrides and the proxy ask. How the claims are delivered is a separateStealthPolicy, set withpolicy,with_policyand shortcuts in any order; a policy never changes a setting. Seedocs/design/fingerprint-api-v2.md. - Profiles (
src/profile_payloads/) translate an external anti-detect profile payload into aBrowserConfigcarrying a concreteFingerprint. - Native vs. JS spoofing is toggled by
StealthPolicy::native_spoofing. When it is on, the CDP overrides and launch arguments carry the values the browser can supply itself (user agent and Client Hints, screen size, locale), and the providers skip the JavaScript patch for them. For example,window_geometrythen only patchescolorDepthandpixelDepth. WhenBrowserConfig::browser_binary_pathis unset,BrowserSessionalso patches a cached copy of the system Chrome binary (SB_CHROME_BINor the one found on the system) before launch and uses it.
The pure CDP engine (src/sb_cdp, see ADR 002) does not use chromedriver, so layer 1’s chromedriver patch does not apply to it. It reuses safe_cdp_overrides (the cdp_overrides without the proxy credential header) for layer 3 and session_registry for layer 4.
This stack works on what a page can read inside the browser. The TLS handshake, the HTTP/2 connection start and the header order are outside it; ADR 003 records how the separate stealth scraping layer handles them.
Consequences#
- Positive: Modular providers are easy to extend and test independently.
- Positive: Native spoofing reduces the amount of injected JavaScript and improves performance.
- Negative: Maintaining parity with evolving anti-bot systems requires ongoing provider updates.