Files
reverse-skills-max/vendor-new/xbsReverseSkill/web-js-env-patcher/references/env-native-protection.md

611 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 真实性保护、Proxy 风险与 native-like 行为
每次进入 Node.js 补环境阶段、准备编写 env 模块、或修改任何 WebAPI 对象模型时读取本文件。真实性保护不是等目标 JS 检测到再补,而是补环境默认基线;除非用户明确要求不做保护或不使用 addon,否则必须从补环境初始化开始执行。
新版 addon API 的详细调用方式见 `references/addon-api.md`。进入补环境阶段、修改 addon helper、创建构造函数 / 原型链 / 集合对象 / `document.all` / `navigator.plugins` / `navigator.mimeTypes` 时必须同时读取该文件。
如果用户明确选择 `isolated-vm` 补环境框架,则必须同时读取 `references/runtime-frameworks.md` 与 `references/xbs-isolated-vm-api.md`。该模式下不要把旧 `addon.node` 桥接进 isolated-vm;补环境代码运行在 isolated-vm Context 内,addon-first 应体现为 `window.xbs` / `globalThis.xbs` native-first。`xbs.createProtoChains`、`xbs.createNativeFunction`、`xbs.createGetter`、`xbs.createSetter`、`xbs.createNativeCollection`、`xbs.getMimeTypesAndPlugins`、`xbs.createUndetectable` 等 API 与 addon-first 同等作为优先主路径。
## 补环境初始化硬性基线(不等待检测)
进入补环境阶段后,先执行以下基线,再运行目标 JS:
1. 先加载 / 检测 native 能力:普通 Node runtime 运行 `scripts/load_native_addon.js --json` 或等价加载随包 `addon.node`;如果用户选择 `isolated-vm`,运行 `scripts/check_xbs_isolated_vm.js --markdown` 并在 Context 内自检 `window.xbs`。记录 available、path、exports / API、Node ABI、平台和失败原因。
2. 从第一版 env 骨架开始就使用 `assets/env-modules/native-protect.js` 或等价 addon-first helper;不要先用普通函数 / 普通赋值跑通,等检测到 `toString`、descriptor、原型链问题后再补保护。
3. 所有新增 WebAPI 默认使用 `Object.defineProperty` / `defineProperties`,并显式设置 `writable`、`enumerable`、`configurable`。
4. 所有方法、构造函数、getter、setter、实例对象都默认做 native-like / toString / `Symbol.toStringTag` / 原型链保护。
5. native 能力可用时必须优先调用 addon API 或 xbs API;只有用户明确要求不使用 native 能力、addon / xbs 缺失、ABI 不兼容或 API 调用失败时,才允许 `NativeProtect` / JS fallback,并记录豁免或降级原因。
该基线是规范性要求,不以“目标是否已经检测到”为触发条件。
## Proxy 只用于探测
JS `Proxy` 很适合发现缺失环境,但不适合作为最终交付。
目标 JS 可能通过以下方式发现异常:
```js
Object.keys(obj)
Reflect.ownKeys(obj)
Object.getOwnPropertyDescriptor(obj, 'xxx')
Object.getPrototypeOf(obj)
obj instanceof SomeConstructor
Object.prototype.toString.call(obj)
Function.prototype.toString.call(fn)
'xxx' in obj
obj.constructor.name
```
策略:
| 阶段 | 做法 |
|---|---|
| 初次运行 | 可以使用全量 Proxy 探测访问路径 |
| 中期调试 | 已知对象改为真实结构,只对未知分支继续 Proxy |
| 最终交付 | 尽量无 Proxy,用真实对象、描述符、原型链和 native-like 函数 |
即使 trace 暂时没有出现 `ownKeys`、`getOwnPropertyDescriptor`、`getPrototypeOf`、`toString`、`instanceof` 等信号,新增关键 WebAPI 也应按真实对象、描述符、原型链和 native-like 函数实现;当这些信号出现时,应立即把残留 Proxy 迁移到真实对象。
## 补环境阶段默认真实性清单
从补环境阶段开始,不能只补到“不报错”,也不能只在检测命中后才补保护。凡新增、修改或被 RuyiTrace / Node trace / 目标检测命中的 WebAPI,都必须逐项确认:
1. **原型链**:补构造函数、`prototype.constructor`、`Object.create(Constructor.prototype)`、必要的 `Object.setPrototypeOf` 多级链路,并验证 `instanceof`。
2. **属性描述符**:所有关键属性都用 `Object.defineProperty` / `defineProperties`;明确 `writable`、`enumerable`、`configurable`,不要用普通赋值替代。
3. **访问器**:真实浏览器中是 getter / setter 的属性,必须补成 accessor descriptor;不要为了省事改成 data descriptor。
4. **函数 toString 保护**:普通方法、构造函数、原型方法在 addon 可用时必须先用 `createNativeFunction`;addon 不可用或调用失败时才用 `NativeProtect.setNativeFunc` fallback。
5. **访问器 toString 保护**:getter / setter 本身也是函数;addon 可用时必须先用 `createGetter` / `createSetter`,失败时才用 `NativeProtect.setNativeFunc(getter, "get xxx")` / `setNativeFunc(setter, "set xxx")` fallback。
6. **实例对象 toString 保护**:对 `navigator`、`document`、`localStorage`、`screen`、`location` 等实例,优先用 addon 构造函数 / `createProtoChains` 实例工厂创建真实对象和原型链;addon 创建出的实例通常已经具备正确的 `Object.prototype.toString` 行为,不要再叠加 `markObjectType`、`markObjectToString` 或手写 `Symbol.toStringTag` 伪装。只有 addon 不可用、必须创建普通 JS fallback 对象时,才使用 `Symbol.toStringTag`、`NativeProtect.setObjFunc(obj, "Navigator")` / `markObjectToString`,并记录 fallback 原因。
7. **集合对象**:`HTMLCollection`、`NodeList`、`PluginArray`、`MimeTypeArray`、`DOMTokenList` 等集合对象必须优先用 addon `createNativeCollection` 或 `getMimeTypesAndPlugins`,不得以普通数组 / 普通对象作为主路径。
8. **plugins / mimeTypes**:`navigator.plugins` 和 `navigator.mimeTypes` 必须优先使用 addon `getMimeTypesAndPlugins(config)`;真实浏览器插件数据不同则传入 config 生成一致数据,不能手写 `[]` 或普通对象作为主路径。
9. **特殊对象**:`document.all` 这类 HTMLDDA / 不可检测对象必须优先使用 addon `createUndetectable`;JS fallback 只能标记为近似,不得声称完全一致。
10. **指纹终端 API**:Canvas / WebGL / WebGPU / Audio / 字体 / DOM 几何等指纹 API 必须优先回放真实浏览器采样值,同时保持 API 所在对象的原型链、描述符和 native-like `toString`;不得因为 Node.js 无法真实渲染就把最终流程改成自动化。
建议交付前运行:
```bash
node scripts/check_env_realism.js --case-dir case --markdown
node scripts/check_env_realism.js --case-dir case --require-document-all --require-ruyitrace --require-fingerprint-fixture --markdown
node scripts/check_fingerprint_fixture.js --case-dir case --require canvas,webgl --markdown
```
## 指纹值回放真实性
即使 `toDataURL`、`getParameter`、`getBoundingClientRect` 等方法只是返回 fixture 采样值,也要像浏览器 native API:
- 方法挂在正确 prototype 上,例如 `HTMLCanvasElement.prototype.toDataURL`。
- 函数名、`length`、`toString()` 尽量 native-like。
- 实例对象满足 `Object.prototype.toString.call(canvas)` / `instanceof HTMLCanvasElement` 等目标检测。
- 缺少采样值时抛出明确错误,不能悄悄返回空字符串、空数组或随机值。
- 不使用 `node-canvas` / `headless-gl` 作为最终指纹一致性方案;这些库可用于离线探索,但不能替代真实浏览器采样值。
## toString / native-like 保护优先级
推荐优先级:
1. 可选 native addon 或 xbs isolated-vm 创建 native-like 函数、getter、setter。
2. JS 层真实对象 + 描述符 + 原型链。
3. `NativeProtect` 作为 addon 不可用时的 fallback;必须同时覆盖普通函数、访问器 getter / setter 和实例对象 `Object.prototype.toString`。
4. 现有能力无法覆盖时,再考虑新增 C++ addon API。
## native 能力缺口闭环
如果目标行为经过真实浏览器采样后确认:纯 JS fallback 无法可靠表达,当前 addon.node API 也无法覆盖,用户选择 isolated-vm 时当前 `window.xbs` / `xbs.dom` API 也无法覆盖,则不要继续硬凑补环境代码。必须读取 `references/native-capability-gap.md`,并按以下方式处理:
1. 先排除实现不完整和已有 API 用法错误。比如没有使用 `createProtoChains`、`createNativeFunction`、`createGetter`、`createSetter`、`createNativeCollection`、`createUndetectable`、`createInterceptor` 或 `xbs.dom.createDocument()` 时,先改为 native-first,不得直接判定为能力缺口。
2. 确认为能力缺口后,写入 `case/notes/native-capability-gap.md`,记录阻塞点、真实浏览器基线、纯 JS / addon / xbs 当前结果、无法解决原因、建议新增 native API、最小行为测试用例和通过标准。
3. 测试用例必须覆盖导致阻塞的关键行为,例如 `typeof`、`Boolean`、`== null`、descriptor、prototype、`Object.prototype.toString`、`instanceof`、DataCloneError 或 Error stack 等,不得只覆盖当前报错消失。
4. 用户更新 addon.node 或 xbs isolated-vm 后,先运行能力缺口测试用例。测试通过后才能把该点标记为已解决;测试不通过时继续阻塞。
5. 用户选择临时 JS workaround 时,只能写成“仅当前样本路径临时兼容”,不得写成稳定浏览器行为。用户拒绝扩展 native 能力且目标参数生成必须依赖该行为时,标记 case 阻塞。
典型场景包括 HTMLDDA / `document.all`、内部槽 brand check、跨 Realm 对象一致性、不可检测对象、structuredClone / postMessage DataCloneError、Error stack 与其他必须依赖 V8 / 浏览器引擎能力的行为。
## addon-first 门禁
进入补环境阶段第一步必须先检测 native 能力;普通 Node runtime 检测 addon,选择 isolated-vm 时检测 xbs isolated-vm;交付前还要复查 addon-first / xbs native-first 证据:
```bash
node scripts/load_native_addon.js --json
node scripts/check_xbs_isolated_vm.js --markdown # 仅用户选择 isolated-vm 时必跑
```
实现规则:
1. `load_native_addon.js` 返回 `{ available, addon, path, exports }`;env helper 必须同时兼容 raw addon 和这个包装对象,不能因为传入 `{ addon }` 而静默跳过 addon。
2. 创建函数、构造函数、getter、setter、集合对象、`navigator.plugins` / `mimeTypes`、`document.all`、`createNativeObject` / `createProtoChains` 支持的对象时,先调用 addon API;如果选择 xbs isolated-vm,则先调用 Context 内的 `xbs` API。
3. 只有用户明确要求不使用 addon、addon 不可用、ABI 不兼容或 API 抛错时,才降级到 `NativeProtect` / JS fallback,并在 `notes`、阶段输出和最终总结中记录豁免或降级原因。
4. 交付前运行 `check_env_realism.js --case-dir case --markdown`;该脚本默认强制 addon-first / xbs native-first。如果源码只出现 `NativeProtect` / `markNativeFunction`,却没有 addon API、`window.xbs` / `globalThis.xbs`、`options.addon` / `options.xbs` 或加载记录,应视为失败。只有用户明确要求不使用 native 能力时,才可传入 `--no-require-addon-first` 并记录原因。
## 构造函数报错必须与浏览器一致
构造函数行为不只包括“能不能 `new`”,还包括错误类型、错误对象构造器、错误信息和调用路径。不要把所有不可构造对象都简单写成:
```js
throw new TypeError('Illegal constructor');
```
要求:
1. 对进入补环境范围的每个构造函数,先用已确认取证浏览器采样:
- `Ctor()` 直接调用的 `error.name`、`error.constructor.name`、`error.message`、`String(error)`、stack 首行。
- `new Ctor()` 构造调用的同样字段。
- 可构造对象的返回实例、`instanceof`、`Object.prototype.toString.call(instance)`。
2. 把采样结果写入 `case/fixtures/constructor-errors.fixture.json`、`case/notes/构造函数行为采样.md` 或阶段报告,不要只凭记忆猜测。
3. 多数 DOM 构造错误是 `TypeError`,但仍以目标浏览器采样为准;如果采样为 `DOMException` 或其他错误类型,必须按样本复现。
4. addon 可用时优先使用 `addon.throwTypeError(message)` 或 addon 提供的等价错误抛出能力;addon 不支持目标错误类型时,才用 JS fallback,并记录差异。
5. 错误信息要精确到浏览器版本和调用方式。例如 Chrome 中“需要 new 调用”和“非法构造”通常不是同一条 message。
推荐采样模板:
```js
function collectConstructorError(name) {
const Ctor = globalThis[name];
const result = { name, call: null, construct: null };
for (const mode of ['call', 'construct']) {
try {
if (mode === 'call') Ctor();
else Reflect.construct(Ctor, []);
result[mode] = { ok: true };
} catch (error) {
result[mode] = {
ok: false,
errorName: error && error.name,
errorConstructor: error && error.constructor && error.constructor.name,
message: error && error.message,
string: String(error),
stackFirstLine: String(error && error.stack || '').split('\n')[0],
};
}
}
return result;
}
```
交付前用 `check_webapi_addon_coverage.js` 检查泛化 `Illegal constructor` 和错误类型风险;发现问题时先补采样,再修复构造函数。
## 可选 native addon
本 Skill 支持可选 `addon.node`,并随包放置在 `assets/native-addon/<platform>-<arch>/addon.node`。脚本会按当前 `process.platform-process.arch` 自动加载;如果当前平台缺失、Node ABI 不兼容或加载失败,必须降级到 JS fallback,并在报告中说明。用户也可以通过 `--addon` 或环境变量 `WEB_JS_ENV_PATCHER_ADDON` 显式覆盖路径,但不要在 Skill 中写入任何本机绝对路径。addon 可用时,“可选”只表示不是跨平台强依赖,不表示补环境时可以跳过;凡 addon 支持的 native-like 能力都必须优先调用 addon,除非用户明确要求不使用 addon。
已知可用 API:
```js
createNativeObject
createNativeFunction
createProtoChains
getProtoChainRegistry
deleteProtoChainRegistryEntry
clearProtoChainRegistry
getPrivate
setPrivate
hasPrivate
deletePrivate
createInterceptor
createNativeCollection
getMimeTypesAndPlugins
createGetter
createSetter
createUndetectable
throwTypeError
```
新版调用约束:
- 新代码创建构造函数、原型链、别名和实例工厂时,优先使用 `createProtoChains(descriptors)`。
- `createNativeObject(options)` 仅作为旧式单对象兼容;不要把 `createNativeObject(tag, proto, properties)` 写入新代码主路径。
- 发现旧式 `createProtoChains(name, chain)` 时要迁移为 descriptors 数组;兼容层只能记录 fallback,不能把旧式形态继续作为 addon 主调用。
- `createUndetectable(callback, handlers)` 支持 `getter`、`setter`、`query`、`deleter`、`enumerator`、`definer`、`descriptor` 等 handlers;处理 `document.all` 时优先使用。
- `createProtoChains` 支持 `constructorBehavior`、`callBehavior`、`constructorErrorMessage`、`callErrorMessage`、`illegalConstructor`、`prototypeMethods`、`staticMethods`、`internalClassName`、`aliases`、`aliasOf`、`instanceFactoryName`、`instanceInitializer`;应一次性规划对象模型,减少后续反复改注册表。
- `getMimeTypesAndPlugins([config])` 是 `navigator.plugins` / `navigator.mimeTypes` 主路径;`createNativeCollection(options)` 是通用集合对象主路径。
检查方式:
```bash
node scripts/load_native_addon.js --json
node scripts/load_native_addon.js --addon <path-to-addon.node> --json
```
常见用途:
| 问题 | 推荐 API |
|---|---|
| `document.all` / HTMLDDA 特殊行为 | `createUndetectable` |
| native-like 函数 | `createNativeFunction` |
| native-like getter | `createGetter` |
| native-like setter | `createSetter` |
| 构造函数、实例、别名和实例工厂 | `createProtoChains(descriptors)` |
| 旧式单对象兼容 | `createNativeObject(options)` |
| `navigator.plugins` / `navigator.mimeTypes` | `getMimeTypesAndPlugins(config)` |
| `HTMLCollection` / `NodeList` / 类数组集合 | `createNativeCollection(options)` |
| named / indexed property interceptor | `createInterceptor(options)` |
| 内部私有状态 | `setPrivate` / `getPrivate` / `hasPrivate` / `deletePrivate` |
| 注册表查看与测试隔离 | `getProtoChainRegistry` / `deleteProtoChainRegistryEntry` / `clearProtoChainRegistry` |
| 浏览器式 TypeError | `throwTypeError` |
## `document.all`
`document.all` 的关键行为:
```js
typeof document.all // 'undefined'
document.all == undefined // true
document.all === undefined // false
Boolean(document.all) // false
'all' in document // true
```
addon 可用时:
```js
Object.defineProperty(document, 'all', {
value: addon.createUndetectable(function () {
return undefined;
}),
enumerable: false,
configurable: true,
});
```
addon 不可用时只能近似:
```js
Object.defineProperty(document, 'all', {
value: undefined,
enumerable: false,
configurable: true,
});
```
近似方案不能满足 `document.all !== undefined`,必须在报告中说明。
## native-like 函数
addon 可用时优先:
```js
const querySelector = addon.createNativeFunction(false, 'querySelector', 1, function (selector) {
return null;
});
querySelector.toString();
// function querySelector() { [native code] }
```
getter / setter:
```js
const getUserAgent = addon.createGetter('userAgent', 0, function () {
return userAgent;
});
Object.defineProperty(Navigator.prototype, 'userAgent', {
get: getUserAgent,
enumerable: true,
configurable: true,
});
```
## 多通道 toString 与 DataCloneError 保护
高强度检测不会只调用 `fn.toString()`。凡进入补环境范围的函数、构造函数、getter、setter 都要按以下通道验证:
- `fn.toString()`
- `Function.prototype.toString.call(fn)`
- 目标 JS 先保存 `const FTS = Function.prototype.toString` 后再 `FTS.call(fn)`
- `String(fn)`
- `fn + ""`
- `fn.toString.toString()`
- `structuredClone(fn)` 抛出的 `DataCloneError` message / stack
- `MessagePort.prototype.postMessage(fn)` 抛出的 `DataCloneError` message / stack
用户实测 addon 创建的 native-like 函数对上述多通道 toString 检测没有问题,因此 addon / xbs native-first 仍是第一选择。只有 fallback 到 `NativeProtect` 时,才使用下方带 `structuredClone` / `postMessage` DataCloneError 改写的版本;旧版只 patch `Function.prototype.toString` 的 NativeProtect 不再作为主推荐。
## `NativeProtect` fallback
addon 不可用、ABI 不兼容或调用失败时,可以在目标 JS 所在运行上下文内使用以下 fallback。必须在加载目标 JS 之前执行。
```js
class NativeProtect {
#map = new Map();
#objMap = new Map();
#clonePatched = false;
static #instance = null;
static getInstance() {
if (!NativeProtect.#instance) {
NativeProtect.#instance = new NativeProtect();
const instance = NativeProtect.#instance;
const _toString = Function.prototype.toString;
const patchedToString = {
toString() {
if (instance.#map.has(this)) {
const name = instance.#map.get(this);
return `function ${name || this.name}() { [native code] }`;
}
return _toString.call(this);
}
}.toString;
Object.defineProperty(Function.prototype, "toString", {
value: patchedToString,
writable: true,
enumerable: false,
configurable: true,
});
instance.#map.set(Function.prototype.toString, "toString");
const _objToString = Object.prototype.toString;
const patchedObjToString = {
toString() {
if (instance.#objMap.has(this)) {
const name = instance.#objMap.get(this);
return `[object ${name}]`;
}
return _objToString.call(this);
}
}.toString;
Object.defineProperty(Object.prototype, "toString", {
value: patchedObjToString,
writable: true,
enumerable: false,
configurable: true,
});
instance.#map.set(Object.prototype.toString, "toString");
instance.#patchCloneErrorLeak();
}
return NativeProtect.#instance;
}
constructor() {
if (NativeProtect.#instance) {
throw new Error("NativeProtect类只能实例化一次");
}
}
setNativeFunc(func, name = "") {
this.#map.set(func, name);
}
setObjFunc(obj, name = "") {
this.#objMap.set(obj, name);
}
#patchCloneErrorLeak() {
if (this.#clonePatched) return;
this.#clonePatched = true;
const rawStructuredClone = globalThis.structuredClone;
const rawMessagePortPostMessage =
typeof MessagePort !== "undefined" &&
MessagePort.prototype &&
MessagePort.prototype.postMessage;
if (typeof rawStructuredClone === "function") {
const self = this;
function structuredClone(value, options) {
try {
return rawStructuredClone.apply(this, arguments);
} catch (err) {
self.#rewriteDataCloneError(err, value);
}
}
this.#copyFunctionMeta(structuredClone, rawStructuredClone, "structuredClone");
const desc =
Object.getOwnPropertyDescriptor(globalThis, "structuredClone") || {
writable: true,
enumerable: true,
configurable: true,
};
Object.defineProperty(globalThis, "structuredClone", {
...desc,
value: structuredClone,
});
this.setNativeFunc(structuredClone, "structuredClone");
}
if (typeof rawMessagePortPostMessage === "function") {
const self = this;
function postMessage(value, transferList) {
try {
return rawMessagePortPostMessage.apply(this, arguments);
} catch (err) {
self.#rewriteDataCloneError(err, value);
}
}
this.#copyFunctionMeta(postMessage, rawMessagePortPostMessage, "postMessage");
const desc =
Object.getOwnPropertyDescriptor(MessagePort.prototype, "postMessage") || {
writable: true,
enumerable: true,
configurable: true,
};
Object.defineProperty(MessagePort.prototype, "postMessage", {
...desc,
value: postMessage,
});
this.setNativeFunc(postMessage, "postMessage");
}
}
#rewriteDataCloneError(err, value) {
if (!err || err.name !== "DataCloneError") {
throw err;
}
const fn = this.#findFunction(value);
if (!fn) {
throw err;
}
const fakeSource = this.#getFunctionSource(fn);
const msg = `${fakeSource} could not be cloned.`;
try {
Object.defineProperty(err, "message", {
value: msg,
configurable: true,
});
} catch (_) {}
try {
if (typeof err.stack === "string") {
const lines = err.stack.split(/\r?\n/);
lines[0] = `${err.name}: ${msg}`;
Object.defineProperty(err, "stack", {
value: lines.join("\n"),
configurable: true,
});
}
} catch (_) {}
throw err;
}
#getFunctionSource(fn) {
try {
return Function.prototype.toString.call(fn);
} catch (_) {
return "function () { [native code] }";
}
}
#findFunction(value, seen = new WeakSet()) {
if (typeof value === "function") return value;
if (value === null || typeof value !== "object") return null;
if (seen.has(value)) return null;
seen.add(value);
if (value instanceof Map) {
for (const [k, v] of value) {
const fk = this.#findFunction(k, seen);
if (fk) return fk;
const fv = this.#findFunction(v, seen);
if (fv) return fv;
}
}
if (value instanceof Set) {
for (const v of value) {
const fv = this.#findFunction(v, seen);
if (fv) return fv;
}
}
for (const key of Reflect.ownKeys(value)) {
let desc;
try {
desc = Object.getOwnPropertyDescriptor(value, key);
} catch (_) {
continue;
}
if (desc && "value" in desc) {
const f = this.#findFunction(desc.value, seen);
if (f) return f;
}
}
return null;
}
#copyFunctionMeta(target, source, name) {
try {
Object.defineProperty(target, "name", {
value: name || source.name,
writable: false,
enumerable: false,
configurable: true,
});
} catch (_) {}
try {
Object.defineProperty(target, "length", {
value: source.length,
writable: false,
enumerable: false,
configurable: true,
});
} catch (_) {}
}
}
```
使用示例:
```js
const nativeProtect = NativeProtect.getInstance();
function getItem(key) {
return storageMap.get(String(key)) ?? null;
}
nativeProtect.setNativeFunc(getItem, 'getItem');
Object.defineProperty(Storage.prototype, 'getItem', {
value: getItem,
writable: true,
enumerable: true,
configurable: true,
});
```
注意:
- 只在目标 JS 所在运行上下文内 patch,不要污染宿主 Node.js 全局环境;该上下文可以是 `vm`、独立 Node 进程或显式隔离的全局对象。
- `NativeProtect` 是 fallback,不是第一选择;如果 addon 可加载但源码直接手写函数再 `setNativeFunc`,应回退修改为 addon-first。
- 使用 `NativeProtect` fallback 时必须使用带 `structuredClone` / `MessagePort.prototype.postMessage` DataCloneError 改写的版本;如果目标会通过 `structuredClone(fn)` 或 `postMessage(fn)` 检测函数源码,旧版 NativeProtect 会暴露非 native 函数。
- 如果目标 JS 提前保存了原始 `Function.prototype.toString`,后 patch 可能失效。
- 如果使用 `vm`,要确保 patch 发生在目标 JS 所在 context 内;如果不用 `vm`,要确保入口文件以独立进程 / 隔离 global 初始化并在结束后不污染其他任务。
## 属性描述符、访问器与原型链
常见检查:
```js
Object.getOwnPropertyDescriptor(navigator, 'userAgent')
Object.getOwnPropertyDescriptor(window, 'navigator')
Object.getPrototypeOf(document)
navigator.constructor.name
```
处理原则:
- 所有关键属性都用 `Object.defineProperty`,禁止用普通赋值替代关键 WebAPI。
- 对不可写、不可枚举、只读 getter 要显式设置;真实浏览器是 accessor 的属性不得降级为 data descriptor。
- getter / setter 的 `Function.prototype.toString.call(descriptor.get)` / `descriptor.set` 也要 native-like。
- `constructor` 通常不可枚举,并且构造函数本身也要做 toString 保护。
- `Symbol.toStringTag` 与 `NativeProtect.setObjFunc(obj, "Xxx")` 只能用于 JS fallback 的 `[object Xxx]`;addon 创建的实例不要再额外调用 `markObjectType` / `markObjectToString`。
- 发现 `instanceof`、`Object.getPrototypeOf`、`constructor.name` 检测时,补构造函数和完整原型链。
## 网络代理检测
如果用户所说“代理检测”是网络代理 / IP 检测,而不是 JS `Proxy`:
- 默认回退到用户真实浏览器手动取证。
- 不默认启用网络代理。
- 如果必须使用代理,必须由用户提供授权代理并确认使用范围。
- 代理被检测时,不尝试绕过风控;应暂停并让用户选择:
1. 换回用户真实浏览器手动取证。
2. 提供 HAR / cURL / JS 文件离线分析。
3. 在授权环境中更换合规网络环境。