chore(release): 0.27.0-fork.30 — stealth: navigator overrides on prototype, not instance
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled

rebrowser's navigatorWebdriver probe checks Object.getOwnPropertyNames(navigator)
== [] (real Chrome keeps navigator members on Navigator.prototype). The launch-mode
stealth script defined language/languages/userAgentData/contacts as instance
own-properties, leaking them as an automation tell.

- add __abRedefineNavProto(name, getterImpl): redefines a navigator member on the
  PROTOTYPE with a native-masked getter toString, then deletes any instance shadow
  (mirrors the existing vendor patch). Falls back to instance only if proto is locked.
- convert language/languages/userAgentData to it; make the contacts block prototype-first.

After: Object.getOwnPropertyNames(navigator) == [], values intact, getters native,
rebrowser navigatorWebdriver 🟢, runtimeEnableLeak/pwInitScripts 🟢, sannysoft 0 fails.
This commit is contained in:
leeguooooo
2026-06-10 11:35:35 +09:00
parent 839aaa5586
commit d1fbdaadeb
4 changed files with 42 additions and 25 deletions
+1 -1
View File
@@ -45,7 +45,7 @@ dependencies = [
[[package]]
name = "agent-browser-stealth"
version = "0.27.0-fork.29"
version = "0.27.0-fork.30"
dependencies = [
"aes-gcm",
"async-trait",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "agent-browser-stealth"
version = "0.27.0-fork.29"
version = "0.27.0-fork.30"
edition = "2021"
description = "Fast browser automation CLI for AI agents"
license = "Apache-2.0"
+39 -22
View File
@@ -1,4 +1,31 @@
const __abStealth = { locale: "en-US", languages: ["en-US", "en"], allowWebGLContextFallback: false, hideCanvas: false, canvasSeed: 0 };
// Redefine a navigator property on its PROTOTYPE (Navigator / WorkerNavigator),
// the way real Chrome exposes these — as prototype getters, NOT instance own
// properties. Adding an own property to the `navigator` instance is itself a
// detectable automation tell: real Chrome's `Object.getOwnPropertyNames(navigator)`
// is empty, so any name we leave on the instance is caught by rebrowser's
// `navigatorWebdriver` probe and similar checks. We mirror the proven `vendor`
// patch below: define on the prototype, native-mask the getter's toString, then
// delete any instance shadow. Falls back to an instance define only if the
// prototype is locked. (A top-level `const` like this is script-scoped, not a
// `window` property, so it does not leak — same as `__abStealth` above.)
const __abRedefineNavProto = (name, getterImpl) => {
try {
const proto = Object.getPrototypeOf(navigator);
const nativeGet = Object.getOwnPropertyDescriptor(proto, name) && Object.getOwnPropertyDescriptor(proto, name).get;
const getter = function () { return getterImpl(); };
if (nativeGet) {
Object.defineProperty(getter, 'name', { value: 'get ' + name, configurable: true });
Object.defineProperty(getter, 'toString', { value: () => nativeGet.toString(), configurable: true, writable: true });
}
Object.defineProperty(proto, name, { get: getter, configurable: true, enumerable: true });
try { delete navigator[name]; } catch (e) {}
return true;
} catch (e) {
try { Object.defineProperty(navigator, name, { get: () => getterImpl(), configurable: true }); } catch (e2) {}
return false;
}
};
(function(){
// Prefer the CDP-level automation override (Emulation.setAutomationOverride),
// which makes navigator.webdriver report `false` NATIVELY — undetectable by
@@ -354,18 +381,8 @@ const __abStealth = { locale: "en-US", languages: ["en-US", "en"], allowWebGLCon
const config = (typeof __abStealth === 'object' && __abStealth) ? __abStealth : null;
if (!config || !Array.isArray(config.languages) || config.languages.length === 0) return;
const locale = typeof config.locale === 'string' ? config.locale : config.languages[0];
try {
Object.defineProperty(navigator, 'language', {
get: () => locale,
configurable: true,
});
} catch {}
try {
Object.defineProperty(navigator, 'languages', {
get: () => config.languages.slice(),
configurable: true,
});
} catch {}
__abRedefineNavProto('language', () => locale);
__abRedefineNavProto('languages', () => config.languages.slice());
})();
(function(){
const ua = String(navigator.userAgent || '');
@@ -1055,10 +1072,15 @@ const __abStealth = { locale: "en-US", languages: ["en-US", "en"], allowWebGLCon
return false;
}
};
if (defineContacts(navigator)) return;
try {
defineContacts(Object.getPrototypeOf(navigator));
} catch {}
// Prototype-first (like the vendor patch): real Chrome exposes navigator
// members on the prototype, not as instance own properties. Define on the
// prototype and remove any instance shadow so Object.getOwnPropertyNames(navigator)
// stays empty; fall back to the instance only if the prototype is locked.
if (defineContacts(Object.getPrototypeOf(navigator))) {
try { delete navigator.contacts; } catch {}
return;
}
defineContacts(navigator);
})();
(function(){
const ContentIndexCtor = typeof ContentIndex === 'function'
@@ -1265,12 +1287,7 @@ const __abStealth = { locale: "en-US", languages: ["en-US", "en"], allowWebGLCon
}
return values;
};
try {
Object.defineProperty(navigator, 'userAgentData', {
get: () => patched,
configurable: true,
});
} catch {}
__abRedefineNavProto('userAgentData', () => patched);
})();
(function(){
const ua = navigator.userAgent;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "agent-browser-stealth",
"version": "0.27.0-fork.29",
"version": "0.27.0-fork.30",
"description": "Browser automation CLI for AI agents — stealth fork with anti-detection",
"type": "module",
"packageManager": "pnpm@11.1.3",