Files
soft_quay/docs/tasks/T-605.md

8.6 KiB
Raw Permalink Blame History

id, title, phase, deps, status, created, issue, context_ref, claim_branch, work_branch, write_paths
id title phase deps status created issue context_ref claim_branch work_branch write_paths
T-605 统一 Windows 安全路径校验并封堵 ZIP 逃逸 1
T-102
T-201
T-202
T-604
DONE 2026-07-16 null f7a803d944 null agent/codex/T-605
docs/tasks/T-605.md
core/internal/safepath/
core/catalog/parser.go
core/catalog/parser_test.go
core/installer/extractor.go
core/installer/extractor_test.go
core/installer/extractor_windows_test.go
core/storage/installed_app.go
core/storage/installed_app_test.go
schemas/manifest.schema.json
schemas/app.schema.json
schemas/installed-app.schema.json
testdata/zip/README.md
docs/api.md
docs/04-architecture.md
docs/05-coding-rules.md
docs/00-ai-start-here.md
docs/current-state.md

问题 / 背景

Phase 1 安全交叉审核确认,T-102 的 normalizeArchivePath 与 normalizeEntrypoint、T-201 的 Catalog entry_exe 校验、T-202 的 installed-app 文件路径校验都以 POSIX path.Clean 为核心,没有覆盖 Windows 普通 DOS 路径的名称归一化规则。

Windows 会忽略路径段末尾的 ASCII 空格和句点,并保留 CON、PRN、AUX、NUL、COM1~COM9、LPT1~LPT9 等设备名及部分扩展/变体。因此 payload/.. /escape.exe、payload/file.、payload/NUL 等名称能通过当前预检,但进入 filepath.Join、MkdirAll、OpenFile 后可能产生父目录语义、路径别名或设备访问。

Extractor 的正式调用约束是 Catalog 签名与 ZIP SHA-256 已验证后才解压,所以这不是任意网络字节可直接利用的开放 RCE;它仍是签名内容管线中必须关闭的纵深防御缺口。关闭前 T-302 不得把现有 Extractor 作为“无路径穿越”的正式安装基础。

方案

  1. 在 core/internal/safepath 建立 Go 1.20 兼容、无 Windows API 依赖的共享 Windows 安全相对路径策略:
    • 输入统一使用 UTF-8 与 / 分隔。
    • 拒绝空路径、绝对/UNC/盘符/ADS、反斜杠、NUL、控制字符和 Windows 禁止字符。
    • 逐段拒绝空段、.、..、首尾 ASCII 空格和尾随句点。
    • 大小写不敏感地拒绝 DOS 设备名及其在 Win7~Win11 需要兼容的扩展/变体。
    • 分别支持“必须指向文件”和“工作目录可为 .”两类调用语义,避免各模块自行放宽。
    • 提供统一的 Windows 大小写折叠冲突键,替换 catalog/installer/storage 中分散的路径规则。
  2. Catalog entry_exe、Extractor ZIP entry/entrypoint、installed-app files[].path 全部接入共享策略,删除重复的私有 path.Clean 校验。后续 T-302 读取 app.json/files.json 时必须复用该策略,不得再造第四套规则。
  3. Extractor 在创建 staging 前完成全部输出路径规划,并在安全路径校验之后对 filepath.Join(destination, outputPath) 做 destination 包含性兜底:
    • 使用绝对路径与 filepath.Rel/等价方法拒绝绝对结果、.. 或 destination 外目标。
    • 该检查是纵深防御,不能替代逐段 Windows 名称校验。
    • entrypoint 返回路径必须通过相同包含性检查。
  4. 在预检阶段拒绝 Windows 归一化后会碰撞的路径,不把冲突拖到 O_EXCL 写入阶段才失败。
  5. 扩展通用测试与 Windows 原生文件系统测试:
    • payload/.. /escape.exe、尾随点/空格、设备名、设备名带扩展、大小写/别名冲突。
    • 拒绝发生在 staging 创建前,且 destination 外无任何新文件。
    • 合法中文、嵌套目录、普通 .exe 和工作目录 . 保持可用。
  6. 同步 api.md、架构、安全编码规则、三个相关 Schema 与 ZIP 测试说明,明确 JSON Schema 只提供基础形状约束,运行时共享校验器才是 Windows 路径安全权威。

验收要点

  • core/internal/safepath 表驱动测试覆盖 Win7~Win11 共同禁止的尾随空格/点、DOS 设备名及变体、控制字符、Windows 禁止字符、绝对/UNC/盘符/ADS、./.. 和空段。
  • Catalog entry_exe、ZIP entrypoint/输出路径、installed-app files[].path 对同一攻击路径给出一致拒绝结果;仓库不再保留三套独立的安全相对路径实现。
  • ZIP payload/.. /escape.exe 在创建 staging 前被拒绝;测试明确断言 destination 外的 escape.exe 不存在。
  • payload/file 与 payload/file.、payload/name 与 payload/name 、设备名大小写/扩展变体不会被视为不同安全输出。
  • destination 包含性兜底对文件、目录和最终 entrypoint 都生效,且不能通过混合分隔符或路径清理结果绕过。
  • 合法 UTF-8/中文文件名、嵌套路径和 Catalog/installed-app 既有合法样例继续通过。
  • Windows 专属测试在当前可用 Windows 环境实际执行;测试代码须可在 Win7、Win10、Win11 复用。若本任务环境不能覆盖三套系统,执行记录必须写明已测系统和待 T-601 补齐的 VM/真机矩阵。
  • cd core && GOWORK=off GOTOOLCHAIN=go1.20.14 go vet ./... && GOWORK=off GOTOOLCHAIN=go1.20.14 go test -count=1 ./... 通过。
  • ./scripts/verify_phase0.ps1 与 bash scripts/verify_phase0.sh 全绿,modern/Win7 双目标继续构建。
  • python scripts/validate_agent_context.py、python scripts/validate_harness_governance.py 通过。

边界(不改什么)

  • 不处理 zip.OpenReader 前的 EOCD/中央目录预扫描与包大小一致性;按审核顺序另立后续任务。
  • 不实现 payload 文件 Sync、目录 rename/journal 的断电耐久顺序或 Windows FlushFileBuffers;按审核顺序另立后续任务并在 T-302/T-601 验证。
  • 不修改 Catalog canonicalization、Base64 或跨实现签名测试向量。
  • 不实现 app.json/files.json 完整解析、身份比对、SHA-256/package 签名、安装编排或健康检查(T-302/T-402/T-403)。
  • 不放宽现有文件数、展开体积或压缩比硬上限。
  • 不引入第三方路径库,不使用 Go 1.21+ API,不修改 Gio/Go 版本。
  • 不修改、提交或删除用户的 soft_quay.code-workspace。

协作约束

  • 按仓库当前规则由单 Agent 串行执行,不启动子 Agent。
  • 本任务只允许修改 frontmatter 中的 write_paths;发现必须改公共协议字段或扩大到中央目录/耐久性实现时先停止并记录,不得把后续整改夹带进 T-605。
  • T-605 完成、完整验证并提交前,不落成或领取下一个 Phase 1 安全整改任务,不恢复 T-302。

执行记录

  • 2026-07-16:根据 docs/review/phase1-security-review.md 交叉复核定稿的最高优先级整改落成任务;现有全局最大任务为 T-604,因此取 T-605。
  • 2026-07-16:任务风险按定稿校准为“签名内容管线的纵深防御缺口”,同时保留阻断 T-302 的结论;本任务只收口 Windows 路径规则与 destination 纵深检查,中央目录、断电耐久和签名向量继续串行拆分。
  • 2026-07-16:在 agent/codex/T-605 分支领取任务,基线为 f7a803d944d3ef1c782aa974d5114f51d3e00b17;保持单 Agent 串行执行。
  • 2026-07-16:新增 core/internal/safepath,统一拒绝非规范/绝对路径、反斜杠/ADS、控制字符、Windows 禁止字符、首尾 ASCII 空格、尾随句点及 DOS 设备名(含扩展、上标数字和兼容变体);Unicode simple-fold 冲突键同时替换 installer/storage 中分散的 strings.ToLower。
  • 2026-07-16:Catalog entry_exe、ZIP entry/entrypoint、installed-app files[].path 全部接入共享规则;Extractor 在创建 staging 前规划所有绝对输出路径,用 destination 相对包含性检查覆盖文件、目录与最终 entrypoint。攻击矩阵新增 payload/.. /escape.exe、dot-space、设备名、禁止字符和 Unicode 折叠冲突,并断言 staging 外无文件。
  • 2026-07-16:三个相关 Schema 加强可表达的路径形状约束并声明运行时校验器为 Windows 设备名/控制字符的权威;api.md、架构、编码规则和 ZIP testdata 说明同步。Schema JSON/正则 smoke 对合法中文与 ../、嵌套 ..、尾随点、首部空格、双斜杠、? 拒绝均通过。
  • 2026-07-16:Windows 原生测试 TestExtractorRejectsWindowsNormalizedEscapeOnNativeFilesystem 在 Windows 10 专业版 10.0.19045 amd64 通过;测试代码带 windows build tag,可直接用于 Win7/Win11。当前环境没有 Win7/Win11 VM,两者实际矩阵按验收边界留 T-601 补齐。
  • 2026-07-16:验证通过:Go 1.20.14 + GOWORK=off 执行 go vet ./...、go test -count=1 ./... 与 Windows 定向测试;./scripts/verify_phase0.ps1;bash scripts/verify_phase0.sh;modern Go 1.25.0 与 Win7 Go 1.20.14 双目标测试/构建;python scripts/validate_agent_context.py;python scripts/validate_harness_governance.py。