fix(stealth): use getter-based webdriver override to match native Chrome shape
CreepJS detects three things for webDriverIsOn: 1. Property deletion (navigator.webdriver === undefined) 2. Value check (!!navigator.webdriver) 3. Lie detection (descriptor tampering via lieProps) Changed from delete/defineProperty-value approach to replacing the CDP getter with a getter returning false, matching the native descriptor shape. Note: 33% headless in CreepJS is a CDP-inherent signal (lieProps detects the getter replacement). This cannot be eliminated at the JS layer since CDP sets the webdriver getter before init scripts run. Real-world impact is minimal — Cloudflare Turnstile passes successfully. Also confirmed: Chrome's remote_debugging preference in Local State persists across restarts, so users only need to enable CDP once via chrome://inspect/#remote-debugging. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
81cdd3b216
commit
320bb61de3
+27
-15
@@ -15,23 +15,35 @@ const STEALTH_SCRIPTS_RAW: &str = include_str!("stealth_scripts.js");
|
||||
/// Only removes navigator.webdriver — the browser's own fingerprint is already real.
|
||||
const MINIMAL_STEALTH_SCRIPT: &str = r#"
|
||||
(function(){
|
||||
// CDP sets a getter on Navigator.prototype.webdriver that returns true.
|
||||
// Simple `delete` won't remove it. We must redefine the property with
|
||||
// Object.defineProperty to fully hide it from `'webdriver' in navigator`.
|
||||
const targets = [Navigator.prototype];
|
||||
if (typeof WorkerNavigator !== 'undefined') targets.push(WorkerNavigator.prototype);
|
||||
targets.push(Object.getPrototypeOf(navigator));
|
||||
for (const target of targets) {
|
||||
if (!target) continue;
|
||||
try { delete target.webdriver; } catch {}
|
||||
try {
|
||||
Object.defineProperty(target, 'webdriver', {
|
||||
get: undefined,
|
||||
// CDP sets navigator.webdriver = true via a getter on Navigator.prototype.
|
||||
// CreepJS detects THREE things:
|
||||
// 1. navigator.webdriver === undefined (deletion = suspicious)
|
||||
// 2. !!navigator.webdriver (true = automation)
|
||||
// 3. lieProps (defineProperty tampering = suspicious)
|
||||
//
|
||||
// The correct fix: set it to FALSE using the native property descriptor
|
||||
// pattern, not delete it or use defineProperty tricks.
|
||||
// Normal Chrome has: Navigator.prototype.webdriver as a native getter returning false.
|
||||
// CDP overrides it to return true. We override the value back to false.
|
||||
try {
|
||||
const proto = Navigator.prototype;
|
||||
const desc = Object.getOwnPropertyDescriptor(proto, 'webdriver');
|
||||
if (desc && desc.get) {
|
||||
// CDP sets a getter that returns true. Replace it with a getter
|
||||
// that returns false — preserving the getter/setter shape so lie
|
||||
// detection sees the same descriptor structure as a normal browser.
|
||||
const nativeToString = desc.get.toString();
|
||||
const fakeGet = function webdriver() { return false; };
|
||||
// Match the native toString to avoid toString-based lie detection
|
||||
fakeGet.toString = () => nativeToString;
|
||||
Object.defineProperty(proto, 'webdriver', {
|
||||
get: fakeGet,
|
||||
set: undefined,
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
});
|
||||
delete target.webdriver;
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
})();
|
||||
"#;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user