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

101 lines
8.6 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.
---
id: T-605
title: 统一 Windows 安全路径校验并封堵 ZIP 逃逸
phase: 1
deps: [T-102, T-201, T-202, T-604]
status: DONE
created: 2026-07-16
issue: null
context_ref: f7a803d944d3ef1c782aa974d5114f51d3e00b17
claim_branch: null
work_branch: agent/codex/T-605
write_paths:
- 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`。