Skip to content

[REQ-092] 少报 Content-Length + descriptor 无 size/sha256 ⇒ 截断的文件被当成下载成功 #1111

Description

@jinjunnn

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 无 sizesha256{"ok":true,"bytes":1048576,"sha256":"62cee74bd18a6895a5c0260025ed08c8fc0ae6ea73efa70576d911fd08510a77","verification":"unverified","via":"stream"}
落盘校验(独立 python3 hashlib)1048576 字节,摘要正是夹具前 1 MiB 的摘要 —— 逐字节确认是截断,不是别的内容
同上但 descriptor 带 sizesize-mismatch,零残留 ✅
同上但 descriptor 带正确 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

  • 只谈 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

Metadata

Metadata

Assignees

Labels

area:contractsContracts, schemas, or compatibilityarea:runtimeServices, jobs, infrastructure, or operationstype:bugSomething is incorrect or regressed

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions