What happens
平台少报Content-Length,而 descriptor 既没有 size 也没有 sha256(契约允许的
verification.status: "unverified" 形态)⇒ downloadArtifactToFile 把被截断的前缀当成
成功的完整下载:落盘、原子 rename、{ok:true} 回 renderer、registerDownloadedArtifact 入 manifest。
实测(alpha-code#402 取证矩阵格 3,ac-402 @ base alpha8ba7d11,未改生产代码):
| 条件 | 结果 |
|---|
origin 声明 content-length: 1048576,实际有 3145728 字节可发,descriptor 无 size 无 sha256 | {"ok":true,"bytes":1048576,"sha256":"62cee74bd18a6895a5c0260025ed08c8fc0ae6ea73efa70576d911fd08510a77","verification":"unverified","via":"stream"} |
落盘校验(独立 python3 hashlib) | 1048576 字节,摘要正是夹具前 1 MiB 的摘要 —— 逐字节确认是截断,不是别的内容 |
同上但 descriptor 带 size | size-mismatch,零残留 ✅ |
同上但 descriptor 带正确 sha256 | sha256-mismatch,零残留 ✅ |
node 22.22.3 与 ELECTRON_RUN_AS_NODE=1 electron 42.3.3 / node 24.15.0 上一致复现。
Why
undici 按 Content-Length 截流:超出声明长度的字节从来不会交给客户端。于是
writeChunksChecked 里那条「累计越界即 abort」永远不会触发,而
opts.declaredLength !== undefined && bytes !== opts.declaredLength 这条检查里
bytes === declaredLength 恰好成立 ⇒ 判成功。
内存安全那一面是满足的(多余字节不进内存);不满足的是「不留下成功外观的 final 文件」。
Why the existing test doesn't catch it
packages/ui-mac/src/main/alpha-artifact-download.test.ts 的
lying Content-Length (under-declares) → abort at cumulative overrun 用的是手搓的
{status, ok, headers, body} 假 Response。它没有 HTTP 框定,假 body 会把超出
content-length 的字节继续交给写入器 ⇒ 越界分支被走到 ⇒ 用例绿。真 HTTP 上不存在那条路。
Scope
- 只谈 descriptor 既无
size 又无 sha256 的那一支;带任一者都已正确拒绝。 - 不含修法建议的实现选择;可能的方向不止一种(拒绝无 digest 且长度不可信的下载 /
把「声明长度 == 实收」降级为不充分证据 / 在 unverified 形态下要求平台补 digest),由实现方裁决。
Evidence
docs/verification/2026-08-25-req092-402-artifact-transfer/(README §格 3,
results/cell3.json 用例 C3.7 / C3.8 / C3.12 / C3.13 / C3.14)。
探针用的是裸 socket origin(probes/origin-raw.ts),先经 probes/origin-calibration.ts 9/9 标定
——第一版基于 Bun.serve 的 origin 造不出「超大 Content-Length」这个条件,已被推翻并记在 README §1。
Refs jinjunnn/alpha-work#1
What happens
平台少报
Content-Length,而 descriptor 既没有size也没有sha256(契约允许的verification.status: "unverified"形态)⇒downloadArtifactToFile把被截断的前缀当成成功的完整下载:落盘、原子 rename、
{ok:true}回 renderer、registerDownloadedArtifact入 manifest。实测(alpha-code#402 取证矩阵格 3,
ac-402@ basealpha8ba7d11,未改生产代码):content-length: 1048576,实际有 3145728 字节可发,descriptor 无size无sha256{"ok":true,"bytes":1048576,"sha256":"62cee74bd18a6895a5c0260025ed08c8fc0ae6ea73efa70576d911fd08510a77","verification":"unverified","via":"stream"}python3 hashlib)sizesize-mismatch,零残留 ✅sha256sha256-mismatch,零残留 ✅node 22.22.3 与
ELECTRON_RUN_AS_NODE=1 electron 42.3.3 / node 24.15.0上一致复现。Why
undici 按
Content-Length截流:超出声明长度的字节从来不会交给客户端。于是writeChunksChecked里那条「累计越界即 abort」永远不会触发,而opts.declaredLength !== undefined && bytes !== opts.declaredLength这条检查里bytes === declaredLength恰好成立 ⇒ 判成功。内存安全那一面是满足的(多余字节不进内存);不满足的是「不留下成功外观的 final 文件」。
Why the existing test doesn't catch it
packages/ui-mac/src/main/alpha-artifact-download.test.ts的lying Content-Length (under-declares) → abort at cumulative overrun用的是手搓的{status, ok, headers, body}假Response。它没有 HTTP 框定,假 body 会把超出content-length的字节继续交给写入器 ⇒ 越界分支被走到 ⇒ 用例绿。真 HTTP 上不存在那条路。Scope
size又无sha256的那一支;带任一者都已正确拒绝。把「声明长度 == 实收」降级为不充分证据 / 在 unverified 形态下要求平台补 digest),由实现方裁决。
Evidence
docs/verification/2026-08-25-req092-402-artifact-transfer/(README §格 3,results/cell3.json用例 C3.7 / C3.8 / C3.12 / C3.13 / C3.14)。探针用的是裸 socket origin(
probes/origin-raw.ts),先经probes/origin-calibration.ts9/9 标定——第一版基于
Bun.serve的 origin 造不出「超大 Content-Length」这个条件,已被推翻并记在 README §1。Refs jinjunnn/alpha-work#1