Files

15 KiB
Raw Permalink Blame History

Node.js 补环境调试循环

当前置阶段已经完成,并且用户确认进入补环境阶段时读取本文件。

目标

补环境阶段不是为了让目标 JS “勉强不报错”,而是为了让目标网页原始 JS 在 Node.js 中生成与浏览器样本一致的结果。

核心原则:

先记录,后补齐;先最小,后扩展;真实性保护和 addon-first 从第一版 env 骨架开始;先样本验证,后交付。

对 Canvas / WebGL / WebGPU / Audio / 字体 / DOM 几何等浏览器指纹,额外遵循:

先采真实浏览器终端 API 返回值,再在 Node.js 中回放;不要强行复刻渲染管线,不要把最终流程改成自动化浏览器。

真实性与 addon-first 额外遵循:

先加载 / 记录 addon,再编写 env;toString、属性描述符、访问器、原型链和实例对象保护是默认基线,不等待检测触发。

如果本 case 的取证模式是 ruyiPage + RuyiTrace,或用户明确说“已经 trace 好日志 / 已提供 NDJSON”,补环境阶段再增加一条硬规则:

先看 RuyiTrace NDJSON,再跑 Node trace;先用浏览器内核日志定位环境依赖,再用 Node.js 复现和固化。

进入条件

进入本阶段前必须确认:

  • 目标属于网页端 JS。
  • 已有目标 API、请求方法、加密参数名和参数位置。
  • 至少有一份成功请求样本,最好有多组样本。
  • 已经定位或初步定位加密入口。
  • 相关 JS 文件已经保存到本地,或确认可以获取。
  • 已经整理 source → entry → builder → writer 四层链路,至少确认 writer。
  • 如用户选择 ruyiPage + RuyiTrace,已导入 NDJSON 日志并生成 notes/ruyitrace-summary.md;如果尚未导入,应先暂停要求用户提供或采集日志,除非用户明确确认无法提供。
  • 已经检查 Node 泄露阻断,不把 process/Buffer/require/module/global 暴露给目标 JS。
  • 已经在补环境初始化阶段加载 / 检测 addon,并决定 addon-first 或记录用户明确豁免 / addon 不可用降级原因。
  • 用户未明确要求关闭真实性保护;默认会对新增 WebAPI 执行属性描述符、访问器、原型链、函数 / 访问器 / 实例对象 toString 保护。
  • 已经执行六项纯计算预检,或明确该目标不依赖相关差异。
  • 如果目标访问 Canvas / WebGL / WebGPU / Audio / 字体 / DOM 几何等指纹 API,已经采集或计划采集真实浏览器终端 API 返回值;缺少样本时不得进入“静默伪造默认值”的交付模式。
  • 如果目标是验证码接口,已确认事件轨迹 fixture 或旧轨迹样本来源,并明确该轨迹只用于补环境生成加密参数,不代表最终验证码验证成功。
  • 用户已确认进入 Node.js 补环境阶段。

如果缺少任一关键条件,先回到 workflow.md 的前置流程,不要直接写 env.js。

验证码事件轨迹 fixture 门禁

验证码接口补环境时,如果加密参数依赖鼠标、点击、拖动、触摸、滚轮或键盘事件,先使用浏览器取证得到的旧轨迹 fixture 跑通参数生成。该阶段目标是让目标 JS 在 Node.js 中生成加密参数,不要求验证码最终验证通过。

硬性要求:

  • 将轨迹设计为可替换入口,例如 motionTrack、eventFixture、verifyContext、clickPoints、dragPath。
  • 推荐保存到 case/fixtures/motion.fixture.json 或 case/result/src/verify/motion-track.js。
  • 代码中必须有 UTF-8 中文注释,说明旧轨迹来源、用途和后续由 web-verify-patcher 替换。
  • 不得把旧轨迹硬编码成不可替换逻辑,不得宣称旧轨迹能稳定通过验证码。
  • 生成验证码加密参数后,如需识别图片、生成真实轨迹、坐标换算或提交验证,先运行 scripts/check_web_verify_patcher.js --require --markdown,确认 web-verify-patcher 可用后再交接。

RuyiTrace 优先诊断门禁

当取证模式为 ruyiPage + RuyiTrace 时,进入任何 env.js 编写、缺失对象补齐或环境问题排查前,先执行以下门禁:

  1. 确认 case/ruyi-trace/logs/*.ndjson 或用户指定 NDJSON 存在。

  2. 如果尚未导入,先执行:

    node scripts/import_ruyitrace_log.js --input <trace.ndjson> --case-dir case --markdown
    
  3. 先阅读 case/notes/ruyitrace-summary.md,再按需过滤原始 NDJSON。

  4. 针对当前错误或不一致,优先搜索:

    • 缺失对象名:如 navigator、document、screen、localStorage。
    • 缺失方法名:如 getItem、getContext、getParameter、toDataURL。
    • 目标 JS 文件名、入口函数名、writer 附近调用栈。
    • 与目标请求发起时间邻近的 api 调用。
  5. 把命中证据写入 notes/missing-env-priority.md,至少包含 api、stack.file、line、col、所属环境模块、补齐优先级和“RuyiTrace 证据 / Node trace 补充 / 推断”分类,再决定 Level 1/2/3 补齐顺序。

  6. 只有 NDJSON 缺失、未覆盖该逻辑或结论不足时,才把 run_with_trace.js / Proxy trace 作为主要发现来源。

不要在已有可用 RuyiTrace 日志时,直接根据 Node.js 报错盲目补环境。

推荐 case 目录

case/
├── js/
│   ├── original/
│   ├── pretty/
│   └── extracted/
├── requests/
├── fixtures/
├── notes/
├── hooks/
├── env/
├── ruyi-trace/
│   └── logs/
├── browser/
│   └── ruyipage/
├── result/
└── tmp/

使用脚本创建:

node scripts/init_env_case.js --case-dir case --target app.js --entry window.makeSign --param sign --api https://example.com/api/search

目录含义:

目录 用途
js/original/ 原始 JS、chunk、runtime、sourcemap
js/pretty/ 格式化后的 JS
js/extracted/ 抽取出的入口模块或片段
requests/ 脱敏后的 cURL、HAR、请求说明
fixtures/ 浏览器真实样本和期望输出
notes/ 分析笔记、入口定位、环境依赖说明
hooks/ 浏览器 Hook 模板和临时断点脚本;调用栈确认并写入 notes 后立即清理或归档
env/ 分层 env 模块草稿
ruyi-trace/logs/ 用户确认导入的 RuyiTrace NDJSON 原始日志
browser/ruyipage/ ruyiPage 取证配置、非敏感脚本和用户确认保留的材料
result/ 最终规范项目目录;唯一执行入口为 final.js 或 final.py,必要模块可放入 src/;不要放临时 runner、测试脚本或浏览器自动化代码
tmp/ trace、临时 runner、日志、失败产物;每次测试或阶段完成后立即清理

双模式策略

探测模式

探测模式用于发现目标 JS 实际访问了哪些浏览器环境。

允许使用:

  • JS Proxy。
  • getter / setter hook。
  • 函数调用记录。
  • 构造调用记录。
  • trace 文件。

运行示例:

node scripts/run_with_trace.js \
  --target case/js/original/app.js \
  --entry window.makeSign \
  --fixture case/fixtures/sample.fixture.json \
  --trace case/tmp/env-trace.jsonl \
  --summary case/tmp/missing-env.json

探测模式允许对象“不完全像浏览器”,但必须明确标记其用途只是调试,不要把全量 Proxy 作为最终交付。

交付模式

交付模式用于最终稳定运行。

要求:

  • 从第一版可交付 env 开始就启用真实性保护,不等检测命中后再补。
  • 进入本模式前已经加载 / 记录 addon;addon 可用时所有 native-like 函数、getter、setter、特殊对象和原型链 helper 都必须 addon-first。
  • 尽量不用全局 Proxy。
  • 将 trace 中发现的访问路径固化为真实对象结构。
  • 显式定义属性描述符。
  • 建立必要原型链。
  • 对构造函数、普通方法、getter、setter 做 native-like toString 保护;访问器函数也必须保护。
  • 对实例对象设置正确 Object.prototype.toString / Symbol.toStringTag,并对 document.all 等特殊对象优先使用可选 native addon。
  • 不输出调试 trace。
  • 用 fixtures 验证输出。

分层补齐策略

补齐顺序固定为:

  1. Level 1 基础运行层:window/self/globalThis、location、URLSearchParams、Storage、Date、crypto、console、定时器。
  2. Level 2 指纹与真实性层:navigator、screen、document、plugins/mimeTypes、Canvas / WebGL / WebGPU / Audio / 字体 / DOM 几何终端 API 值回放、属性描述符、原型链、native-like toString。
  3. Level 3 目标 SDK 专用层:SDK init、动态 chunk、Worker、WASM、postMessage、站点私有缓存。

模块选择细节见 env-module-levels.md。输出不一致时先读取 node-leakage-and-silent-failure.md,不要盲目继续补对象。

运行顺序

1. 创建 case 目录和 fixtures。
2. 如果取证模式为 ruyiPage + RuyiTrace,先导入 / 阅读 NDJSON 摘要,形成环境依赖优先级。
3. 执行 Node 泄露阻断检查和六项纯计算预检。
4. 运行 native addon 优先门禁:加载 / 检测随包 addon;可用时后续 native-like 函数、访问器、`document.all`、原型链等先走 addon,失败时记录降级状态。
5. 用最小浏览器环境运行目标 JS。
6. 捕获 ReferenceError / TypeError / 输出不一致。
7. 如已选择 RuyiTrace,先用 NDJSON 证据解释缺失路径;解释不足时再用 Proxy 记录实际访问路径。
8. 生成缺失环境报告,并用 `analyze_trace.js` 输出模块优先级。
9. 如果 trace 或 NDJSON 显示 Canvas / WebGL / WebGPU / Audio / 字体 / DOM 几何指纹,先读取 `fingerprint-value-replay.md`,生成采样 Hook,采集 `fixtures/fingerprint.fixture.json`,再接入 `assets/env-modules/fingerprint-env.js` 做终端 API 值回放。
10. 按 RuyiTrace 证据 + Node trace 补充结论,分 Level 1/2/3 补齐环境。
11. 发现 Proxy、toString、descriptor、accessor、prototype、instanceof、constructor.name 或 `document.all` 检测信号时,迁移到真实对象 / addon-first 模式;addon 不可用时才使用 NativeProtect fallback。
12. 如果是验证码接口,先注入可替换的 `motionTrack` / `eventFixture` 旧轨迹 fixture,仅用于生成验证码接口加密参数。
13. 调用加密入口。
14. 和浏览器样本对比;如果缺少指纹样本,补采样而不是改用自动化浏览器。
15. 多样本通过后整理规范项目目录,把补环境、入口调用、参数生成和最终 Node.js / Python 请求逻辑串联到唯一入口 `result/final.js` 或 `result/final.py`;必要模块可放入 `result/src/`。
16. 先运行 `check_env_realism.js --require-addon-first`,确认原型链、属性描述符、访问器、toString 保护、实例对象 toString 保护、addon-first/native fallback 记录、document.all、RuyiTrace 证据和指纹值回放证据;再运行 `check_fingerprint_fixture.js` 与 `check_final_artifact.js`,确认最终主文件不含 ruyiPage / Playwright / Puppeteer / Selenium / CloakBrowser 等自动化代码。
17. 清理 trace、临时日志、失败 runner、测试脚本、指纹采样 Hook 和多余文件。

错误分类

ReferenceError

示例:

ReferenceError: XMLHttpRequest is not defined

记录为:

{
  "type": "missing-global",
  "name": "XMLHttpRequest"
}

处理:如果选择 ruyiPage + RuyiTrace,先在 NDJSON 中确认浏览器真实调用栈、构造方式和相关属性,再补全 globalThis.XMLHttpRequest,必要时建立构造函数和原型。

TypeError:不是函数

示例:

TypeError: localStorage.getItem is not a function

记录为:

{
  "type": "missing-method",
  "path": "localStorage.getItem"
}

处理:如果选择 ruyiPage + RuyiTrace,先在 NDJSON 中确认该方法真实是否被调用、调用参数和调用栈,再补全对应方法。若 addon 可用,优先创建 native-like 函数。

TypeError:读取 undefined 属性

示例:

Cannot read properties of undefined (reading 'userAgent')

处理:优先结合 RuyiTrace NDJSON 判断浏览器真实路径;NDJSON 不足时再结合 Node trace,补齐缺失对象或属性。

输出不一致

如果不再报错但签名不一致,且已选择 ruyiPage + RuyiTrace,先回看目标请求前后的 NDJSON 调用,再检查:

  • 请求 URL、Query、Body 是否完全一致。
  • Date.now、performance.now。
  • Math.random、crypto.getRandomValues。
  • navigator.userAgent、语言、时区、屏幕信息。
  • document.cookie、localStorage、sessionStorage。
  • location.href、origin、pathname。
  • 是否启用 native-like toString。
  • document.all 是否走了 native addon 路径。
  • Object.getOwnPropertyDescriptor 返回的 descriptor 是否与真实浏览器一致。
  • getter / setter / 构造函数 / 原型方法的 Function.prototype.toString.call(...) 是否为 native-like。
  • 实例对象的 Object.prototype.toString.call(...)、constructor.name、instanceof 是否一致。

如果 NDJSON 中没有覆盖目标参数生成时间段,明确标记“RuyiTrace 未覆盖”,再使用 Node trace / Hook / 断点补充。

trace 输出

推荐临时文件:

case/tmp/
├── env-trace.jsonl
├── missing-env.json
├── runtime-error.log
└── env-access-summary.md

env-trace.jsonl 示例:

{"type":"get","path":"navigator.userAgent"}
{"type":"get","path":"document.cookie"}
{"type":"call","path":"localStorage.getItem","args":["string"]}

missing-env.json 示例:

{
  "missingGlobals": ["XMLHttpRequest"],
  "missingMethods": ["localStorage.getItem"],
  "missingProperties": ["navigator.userAgent", "document.cookie"],
  "specialObjects": ["document.all"],
  "nativeAddon": {
    "available": true,
    "usedApis": ["createUndetectable", "createNativeFunction", "createGetter"]
  },
  "proxyRiskSignals": ["Object.getOwnPropertyDescriptor", "Function.prototype.toString"],
  "runtimeErrors": []
}

输出要求

阶段输出应包含:

## Node.js 补环境运行结果

- 目标 JS:
- 入口函数:
- fixtures:
- native addon:可用 / 不可用 / 用户明确豁免;必须说明是否在补环境初始化阶段已加载 / 记录
- 真实性检查:已运行 `check_env_realism.js`(默认 addon-first)/ 未运行(需说明原因)
- RuyiTrace NDJSON:已优先分析 / 未提供 / 未覆盖当前问题
- 运行状态:成功 / 失败

## 缺失环境摘要

- RuyiTrace 证据:api、stack.file、line、col、相关时间窗口
- 缺失全局对象:
- 缺失方法:
- 缺失属性:
- 特殊对象:
- Proxy 检测风险:

## 下一轮补齐计划

1. ...
2. ...

## 清理检查

- [ ] 已清理临时 trace。
- [ ] 已清理失败 runner。
- [ ] 已把关键结论写入 notes。
- [ ] 已生成规范项目目录,且唯一执行入口为 `result/final.js` 或 `result/final.py`。
- [ ] 已确认最终项目所有源码不包含浏览器自动化代码。
- [ ] 已确认最终请求由 Node.js / Python HTTP 客户端实现。
- [ ] 已生成 `result/最终项目总结.md`,除非用户明确要求不生成。