Files

130 lines
6.5 KiB
Markdown
Raw Permalink 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.

# XHR/fetch 请求语义与生命周期审计
本文件用于解决“XHR/fetch 已桥接同一 TLS Session,但请求头、body、请求来源、失败语义、responseURL、事件顺序或 reload 生命周期仍错误”的问题。Session Bridge 只证明请求由哪个客户端发送;本审计证明发送的请求和浏览器行为是否正确。
## 核心原则
- 真实请求前必须先执行 `no-send` 审计:Node bridge 记录完整待发送请求,但不得访问目标网络。
- 浏览器成功样本与 Node no-send 结果必须生成可比较的 network transcript。
- document navigation、reload、image、script、XHR、fetch、sendBeacon 必须有明确 actor,禁止根据 URL 猜请求类型。
- 请求和响应的 `matched` 必须由 `check_xhr_fetch_semantics.js` 计算,不能手工填写。
- Header 名称、顺序、重复字段和 body 原始字节属于请求语义,不得只比较对象键值。
- 缺失字段不得在规范化时用 `""`、`0`、`main` 或数组默认值掩盖。浏览器与 Node 同时缺失同一字段也必须失败。
- transcript 中敏感 Header 可以只记录 `{ "redacted": true, "length": N, "sha256": "..." }`;不允许只保留 Header 名称或空字符串。
## 强制产物
```text
case/fixtures/browser-network-transcript.ndjson
case/tmp/node-network-transcript.ndjson
case/tmp/xhr-fetch-semantics-audit.json
case/notes/xhr-fetch-semantics-diff.md
```
两份 transcript 都必须记录 `baselineId`。Node transcript 还必须记录:
```json
{
"type": "meta",
"schemaVersion": "network-transcript/v3",
"generatedBy": "runtime-network-recorder/v3",
"baselineId": "fp-001",
"mode": "no-send",
"runtimeSourceHash": "...",
"networkAttempts": 0
}
```
浏览器 meta 必须使用 `mode: "browser-baseline"`,并记录 `baselineId` 与可信 `generatedBy/capturedBy`。Node meta 必须使用 `mode: "no-send"`,记录当前源码的完整 SHA-256 `runtimeSourceHash`、可信 `generatedBy` 和显式 `networkAttempts: 0`。
## 每个事件共同字段
- `kind`、`pairKey`、`actor`。
- 显式 `realmId`、`navigationEpoch`、`sequence`,不能依赖默认值。
- `sessionId`;同一 transcript 只允许一个 Session。
## 每个请求必须记录
- method、完整 URL、query。
- 有序 Header 数组,保留重复字段。
- credentials、referrer、origin、Cookie 处理策略。
- body 类型、编码、原始 byte length、SHA256。
- Content-Type 和 multipart boundary。
- redirect、cache、timeout、abort 信息。
`POST`、`PUT`、`PATCH` 或任何实际携带 body 的请求,必须存在 `contentType`、原始 `bodyLength` 和完整 `bodySha256`。没有请求体时也不得伪造调试字符串的 hash。
Header 推荐结构:
```json
[
["content-type", "text/plain;charset=UTF-8"],
["cookie", {"redacted": true, "length": 128, "sha256": "..."}]
]
```
Header 数组中的每一项都必须有非空名称和真实值或脱敏摘要。比较时使用名称、顺序、重复次数、byte length 与 SHA-256;不能只比较名称集合。
浏览器与 Node 的原始 request id 可以不同,但必须在规范化阶段生成相同 `pairKey`。推荐使用“writer phase + actor + realm role + navigation epoch + 该 actor 第 N 次请求”,不得只用 URL 配对,因为同一 URL 可能同时用于 document navigation、reload 和 XHR。
## 每个响应必须记录
- status、statusText、responseURL。
- 响应头有序列表和脚本可见头列表。
- body byte length、SHA256、responseType。
- Set-Cookie 是否写入同一 session Cookie jar。
- readyState、readystatechange、load、error、abort、timeout、loadend 顺序。
- Promise resolve/reject 与 microtask/task 顺序。
- PerformanceResourceTiming 可见时机。
即使响应体为空,也必须记录 `bodyLength: 0` 和空字节 SHA-256。`status > 0` 时必须记录最终 `responseURL`;失败、abort、CORS 或网络错误的 `status=0` 必须显式记录空 `responseURL`。
## 硬性浏览器语义
- 网络错误、CORS 拒绝、abort 等 `status=0` 场景,`responseURL` 必须为空,除非同 baseline 浏览器证据明确相反。
- completion 层不得用 request URL 为失败 XHR 回填 `responseURL`。
- document navigation/reload 不得进入 XHR completion。
- 首次 navigation 与 reload 必须创建各自正确的 document/realm 生命周期。
- reload 或 navigation commit 后,旧 realm 的 pending timer、XHR、fetch、MessagePort task 和 observer callback 必须取消或丢弃。
- 真实浏览器没有发出的 XHR/fetch,Node 不得多发;多余事件直接阻断。
- `getResponseHeader("set-cookie")`、Response Headers 的 Set-Cookie 可见性必须以目标浏览器 baseline 为准。
- browser/Node 必须逐事件保持 request、response、reload/navigation lifecycle 的全局顺序;只按 `pairKey` 分组后忽略相对顺序不能通过。
- Node transcript 必须来自真正执行当前 XHR/fetch 实现的 recorder;手写与浏览器相同的 NDJSON、复制浏览器事件或空值占位都不算证据。
## body 类型矩阵
目标路径涉及时至少覆盖:
- `null` / `undefined`。
- string。
- `URLSearchParams`。
- `FormData` 与 multipart boundary。
- Blob。
- ArrayBuffer / TypedArray。
- Document。
- JSON string。
必须比较发送前 body byte length 和 SHA256,不能只比较序列化后的调试文本。
## Header 矩阵
必须检查:
- `setRequestHeader()` 的 trim、合并和重复行为。
- Header 名称大小写与发送顺序。
- Header 实际值或脱敏后的 byte length / SHA-256;只有名称没有值直接失败。
- 自动 Content-Type。
- Cookie、Origin、Referer、Accept、Accept-Language、Client Hints 等由哪一层生成。
- 禁止脚本设置的 Header 是否被正确过滤。
- curl/HAR 与当前浏览器 baseline 冲突时,以当前 baseline 为准。
## 检查命令
```bash
node scripts/check_xhr_fetch_semantics.js --case-dir case --require --require-no-send --out case/tmp/xhr-fetch-semantics-audit.json --markdown
node scripts/check_xhr_fetch_session_bridge.js --case-dir case --require-live --tls-client curl_cffi --markdown
node scripts/check_environment_closure.js --case-dir case --before-real-request --require-live --tls-client curl_cffi --markdown
```
只有 no-send 语义审计和 session bridge 运行时审计同时通过,才允许少量授权真实请求。不得等用户提出“比较请求头和请求体”后才执行本流程。