# 真实性保护、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/-/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 --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. 在授权环境中更换合规网络环境。