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:
leeguooooo
2026-05-08 23:48:50 +09:00
co-authored by Claude Opus 4.6
parent 81cdd3b216
commit 320bb61de3
+27 -15
View File
@@ -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 {}
})();
"#;