diff --git a/docs/00-ai-start-here.md b/docs/00-ai-start-here.md index 5936923..c8d34ad 100644 --- a/docs/00-ai-start-here.md +++ b/docs/00-ai-start-here.md @@ -47,7 +47,7 @@ SoftBox 软件盒子是一个使用 Go + Gio 开发的 Windows 桌面客户端, ## 当前阶段 -当前项目已完成 Phase 0~2、T-301 与审核整改 `T-604`~`T-613`。Windows 安全路径阻断项、图标缓存资源边界、后台结果回 UI 线程的事件接线、双适配器交互契约、`VisibleItems` 快照生命周期、双端 Gio shell 职责拆分、unsafe cache 安全诊断/runbook、ZIP 中央目录/EOCD(含 ZIP64)预扫描以及安装文件/目录/journal 的代码层耐久顺序均已关闭;物理断电、文件锁与杀毒软件干扰验证仍后置到 T-302/T-601,T-302 暂后置。 +当前项目已完成 Phase 0~2、T-301 与审核整改 `T-604`~`T-613`。Windows 安全路径阻断项、图标缓存资源边界、后台结果回 UI 线程的事件接线、双适配器交互契约、`VisibleItems` 快照生命周期、双端 Gio shell 职责拆分、unsafe cache 安全诊断/runbook、ZIP 中央目录/EOCD(含 ZIP64)预扫描以及安装文件/目录/journal 的代码层耐久顺序均已关闭;`T-614` 是当前唯一任务,用于冻结 Catalog canonicalization/签名跨实现向量。物理断电、文件锁与杀毒软件干扰验证仍后置到 T-302/T-601,T-302 暂后置。 优先路径: @@ -55,7 +55,7 @@ SoftBox 软件盒子是一个使用 Go + Gio 开发的 Windows 桌面客户端, 2. 已完成 Phase 1:清单验签、ZIP 安全解压、原子切换回滚原型。 3. 已完成 Phase 2 与 T-301:清单/列表/详情/图标缓存 + 可恢复下载队列。 4. 已完成 T-604:modern/Win7 workspace 与 Gio 版本解析彻底隔离。 -5. 已完成 T-606~T-613:图标缓存资源边界、UI 线程事件接线、双 Gio 适配器交互契约、`VisibleItems` generation 生命周期、双端 `shell.go` 同 package 镜像职责拆分、unsafe cache 诊断/人工恢复指引、ZIP 中央目录/EOCD 预扫描和安装耐久顺序;下一步先落成并执行独立签名向量任务,再继续 T-302/T-303 与 Phase 4-6。T-302/T-601 仍须补真实 Windows 环境的断电/干扰注入。 +5. 已完成 T-606~T-613:图标缓存资源边界、UI 线程事件接线、双 Gio 适配器交互契约、`VisibleItems` generation 生命周期、双端 `shell.go` 同 package 镜像职责拆分、unsafe cache 诊断/人工恢复指引、ZIP 中央目录/EOCD 预扫描和安装耐久顺序;下一步执行 T-614 的独立签名向量任务,再继续 T-302/T-303 与 Phase 4-6。T-302/T-601 仍须补真实 Windows 环境的断电/干扰注入。 ## 领取任务规则 diff --git a/docs/04-architecture.md b/docs/04-architecture.md index d6cb53b..5b893ce 100644 --- a/docs/04-architecture.md +++ b/docs/04-architecture.md @@ -113,7 +113,7 @@ soft_quay/ - 软件主键是永久稳定的 `id`(小写英文/数字/短横线),不用名称;下架用 `status`,不用名称前缀。 - 清单验签失败时**拒绝**,回退到最后一次验证成功的缓存,绝不接受未验证的新内容。 - Catalog 正式加载顺序为 HTTPS 获取 → 验签 → 严格字段/Schema/channel 校验 → 缓存替换 → 目标过滤;签名正确但结构或目标通道不匹配的远端内容不能挤掉最后可消费缓存。 -- 清单解析拒绝未知字段、重复字段/软件 ID、尾随 JSON、非整数数字、非 HTTPS URL 与 architectures/packages 映射不一致;签名域为移除顶层 `signature` 后的受限规范 JSON,细节见 [api.md](api.md)。 +- 清单解析拒绝未知字段、重复字段/软件 ID、尾随 JSON、`-0`/非整数数字、非法 Unicode surrogate、非唯一 Base64 signature、非 HTTPS URL 与 architectures/packages 映射不一致;签名域为只移除顶层 `signature` 的受限规范 JSON。对象键 Unicode 排序、字符串 JSON 转义、原始大整数 token 和静态跨实现向量是同一协议的一部分,细节见 [api.md](api.md)。 - Catalog `entry_exe` 与后续 app.json/files.json 路径必须复用 `core/internal/safepath`,不能由各协议解析器分别维护 Windows 路径规则。 - channel 分 `modern` / `win7`,更新器必须校验 channel + min_os,禁止交叉升级。 - `category` 提供稳定单分类,`tags` 用于搜索和多标签展示;hidden 项从远端目录隐藏,deprecated 与不兼容项保留可见原因但不提供安装包操作。 diff --git a/docs/05-coding-rules.md b/docs/05-coding-rules.md index ff9cbe4..dc6ff57 100644 --- a/docs/05-coding-rules.md +++ b/docs/05-coding-rules.md @@ -36,6 +36,7 @@ - 任何下载内容未通过 SHA-256 + 签名验证,**不得解压执行**;清单验签失败拒绝,不回退到未验证内容。 - ZIP 与软件包路径必须使用 `core/internal/safepath` 的共享 Windows 安全相对路径策略,拒绝绝对路径、`../`/dot-space 归一化穿越、首尾 ASCII 空格、尾随句点、DOS 设备名、Windows 禁止字符、符号链接逃逸和 entrypoint 指向 payload 外;native 输出路径还必须验证仍在 destination 内。Catalog/storage/app.json/files.json 不得各自复制一套路径规则。 - 任意 `zip.Reader` 构造前,必须以同一普通文件句柄核对已验签 Catalog 的 exact package `size` 与完成下载实际长度,并有界预扫描 EOCD/ZIP64:原始包、中央目录字节数和声明条目数超过硬上限,跨盘/截断/边界不一致元数据一律拒绝;不得先扫描路径后按该路径重新打开,不得仅依赖 `len(archive.File)`。 +- Catalog 签名域只移除顶层 `signature`:JSON 解析必须拒绝非法 surrogate、`-0` 和非整数,大整数保持原始十进制 token;signature 必须是唯一的标准 padded Base64 文本,CR/LF/空白或 padding 变体一律拒绝。跨实现测试只消费 `testdata/catalog/canonical-vectors.json` 的静态预期 bytes/公钥/签名,不得用当前 canonicalizer 或测试私钥自举“正确”向量。 - ZIP 解压必须保留文件数、展开体积和压缩比硬上限。 - 安装/更新只走 `staging → current → backup` 原子流程;任何写 `current/` 的捷径都不允许。 - 安装 payload 在 CRC/长度检查后必须 `Sync` 再 Close;staging 目录按子→根及其父目录建立栅栏。journal 临时文件内容、journal/backup rename 或删除、受控目录 rename 或删除后必须同步 app root,成功 phase 不得越过失败栅栏。Windows 目录栅栏使用 Win7 可用的 `FILE_FLAG_BACKUP_SEMANTICS` + `FlushFileBuffers`,不支持或失败必须 fail closed;单元测试不等同于物理断电证明。 diff --git a/docs/06-tasks.md b/docs/06-tasks.md index 2d5f1f5..914fe93 100644 --- a/docs/06-tasks.md +++ b/docs/06-tasks.md @@ -42,13 +42,14 @@ #### Phase 1 交叉审核加固 -Phase 1 安全整改按 `docs/review/phase1-security-review.md` 的交叉复核定稿顺序串行落成。T-605、T-612 与 T-613 已关闭;T-613 建立文件、目录和 journal 的代码层耐久顺序,物理断电验证仍后置到 T-302/T-601。签名向量任务在前一整改完成并提交后再正式编号,T-302 继续后置。 +Phase 1 安全整改按 `docs/review/phase1-security-review.md` 的交叉复核定稿顺序串行落成。T-605、T-612 与 T-613 已关闭;T-613 建立文件、目录和 journal 的代码层耐久顺序,物理断电验证仍后置到 T-302/T-601。T-614 冻结 Catalog canonicalization/签名跨实现 testdata corpus 与客户端拒绝规则;完成后才进入 T-302。 | ID | 任务 | 依赖 | 验收要点 | | --- | --- | --- | --- | | T-605 | 统一 Windows 安全路径校验并封堵 ZIP 逃逸 | T-102, T-201, T-202, T-604 | Catalog/ZIP/installed-app 共用逐段 Windows 安全相对路径策略;拒绝尾随空格/点与 DOS 设备名;输出路径增加 destination 包含性兜底;原生 Windows 用例证明不写出 staging | | T-612 | 在 ZIP 打开前限制包大小与中央目录元数据 | T-605 | 已验签 Catalog size、已完成普通下载文件长度与同句柄 EOCD/ZIP64 预扫描一致;在 `zip.NewReader` 前限制原始包、中央目录与声明条目数 | | T-613 | 建立安装文件与目录事务耐久顺序 | T-612 | payload Sync、staging tree/journal/rename/remove 的目录栅栏;Windows `FlushFileBuffers` fail-closed;物理断电故障注入仍后置到 T-302/T-601 | +| T-614 | 冻结 Catalog 规范化与签名跨实现测试向量 | T-613 | 静态 canonical bytes/Ed25519 test vectors 覆盖 Unicode、surrogate、`-0`/大整数、嵌套 signature 与 Base64;客户端不自举期望值,外部发布端可消费同一 corpus | ### Phase 2 · 清单与软件列表 diff --git a/docs/api.md b/docs/api.md index ce22a90..9ef3eca 100644 --- a/docs/api.md +++ b/docs/api.md @@ -70,12 +70,12 @@ 客户端采用以下签名域,供发布器实现对齐: -1. 输入必须是单个 UTF-8 JSON object;重复字段、尾随 JSON、浮点/指数数字直接拒绝。 +1. 输入必须是单个 UTF-8 JSON object;重复字段、尾随 JSON、浮点/指数数字、`-0`、前导零和非法 Unicode surrogate 直接拒绝。JSON 字符串中的 `\u` high surrogate 必须立刻与一个 low surrogate 配对;孤立/不匹配 surrogate 不得替换为 U+FFFD 后继续处理。 2. 读取顶层 `signature`(标准 Base64 编码的 64 字节 Ed25519 签名),然后从对象中移除该字段。 -3. 对剩余值递归规范化:对象键按 Unicode 字符串升序排列;数组保持原顺序;字符串按 JSON 转义;数字仅允许 JSON 整数并保持其合法十进制写法;不保留无意义空白。 +3. 对剩余值递归规范化:对象键按 Unicode 字符串升序排列;数组保持原顺序;字符串按 JSON 转义;数字只接受 `0`、正整数或负的非零整数,并保持其原始合法十进制 token(包括大于 IEEE-754 安全整数的值);不保留无意义空白。只删除**顶层** `signature`,任何嵌套 package `signature` 仍属于 signed payload。 4. Ed25519 直接签名/验证上述规范 JSON 字节。 -T-201 已把该签名域接入正式客户端加载链路并用客户端测试向量覆盖。`softbox-catalog` 发布端仍必须补跨实现向量测试;密钥 ID/轮换字段尚未定稿,在单公钥协议升级前不得另造签名域。 +`signature` 文本必须是唯一的标准 padded Base64 表示:严格解码为 64 字节后重新编码必须逐字节等于输入,因此 CR/LF、其他空白、缺失/额外 padding 都拒绝。固定跨实现 corpus 位于 `testdata/catalog/canonical-vectors.json`;客户端与 `softbox-catalog` 发布端必须读取其中的静态 canonical bytes、测试公钥和签名,不得以自身 canonicalizer 重新生成期望值。密钥 ID/轮换字段尚未定稿,在单公钥协议升级前不得另造签名域。 ### 1.2 图标内容引用与本地缓存 diff --git a/docs/current-state.md b/docs/current-state.md index b903176..beeed88 100644 --- a/docs/current-state.md +++ b/docs/current-state.md @@ -13,7 +13,7 @@ ## 当前快照 - 日期:2026-07-18 -- 阶段:Phase 2 已完成(T-201~T-204);Phase 3 的 T-301 可恢复下载队列已完成;审核整改 T-604~T-613 已完成,T-302 继续暂后置 +- 阶段:Phase 2 已完成(T-201~T-204);Phase 3 的 T-301 可恢复下载队列已完成;审核整改 T-604~T-613 已完成,T-614 是当前唯一 TODO,T-302 继续暂后置 - 技术栈:根 Go 1.25 workspace 只纳入 core/app-modern,`app-win7/go.work` 独立纳入 core/app-win7;版本闸门证明 modern Gio v0.10.1 与 win7 Gio v0.6.0 不交叉解析 - 生产代码:core 已有 Catalog/本地状态/存储、共享 Windows 安全相对路径策略、安全 ZIP 解压/回滚原型(Extractor 在同一普通文件句柄上核对 expected Catalog size 后有界预扫 EOCD/ZIP64、中央目录与条目数,每个 payload Sync/Close 后同步 staging tree 及父目录;transaction/switch/rollback/recovery 的 journal、rename、清理经统一 fail-closed 耐久栅栏,Windows 使用目录句柄 FlushFileBuffers)、发布稳定只读 generation 的无 IO 软件列表模型、按 key in-flight + 流式有界读取 + 32 MiB/256-key LRU 的可信图标缓存、图标 Load/Decode 事件发布用例、有界 application event relay,以及默认并发 2 的持久可恢复下载队列;modern/win7 主循环已接 relay/Invalidate,AppShell 已实现搜索/分类/视图、惰性列表、详情右栏、完整图标失败 identity 生命周期与仅 `unsafe_cache` 可见的安全 locator/人工恢复提示,并按 root/header/catalog/detail/style 同 package 镜像职责拆文件 - 测试:core 覆盖 Catalog、列表快照 generation/零复制、SemVer/12 状态、本地安装记录、Windows dot-space/设备名/Unicode 折叠路径攻击、ZIP destination 包含性与 EOCD/ZIP64 原始包/中央目录/条目数预扫描、payload/staging tree/journal/rename/rollback/recovery/cleanup 耐久顺序及错误注入、Windows 原生目录 `FlushFileBuffers`、图标并发/取消/读取边界/LRU、真实目录/symlink fail-closed 与 cache→`unsafe_cache` event、relay 背压与关闭、下载并发/暂停/取消/重试/Range/断连/恢复/事件失败与文件身份替换;两个 app 覆盖 Editor/视图/分类/行/恢复/关闭接线、500 项 viewport、AppID 控件与分类控件生命周期、详情上下文、空状态语义、UI drain 前后、图标失败身份生命周期与 `unsafe_cache` 详情语义;安装恢复矩阵保持通过 @@ -21,14 +21,14 @@ - 标准启动路径:`./init.sh` / `./init.ps1`(同步依赖、执行完整 Phase 0 闸门、打印双目标构建命令) - 标准验证路径:`bash scripts/verify_phase0.sh` / `./scripts/verify_phase0.ps1` - 版本管理:git 已初始化,main 分支,远端 origin 为 Gitea `opc/soft_quay`;harness 文档已提交 -- 当前 blocker:无;T-613 已建立 payload、staging tree、journal 与目录 rename/remove 的 fail-closed 耐久顺序,并由 Windows 原生目录 `FlushFileBuffers` 用例验证。物理断电、文件锁/杀毒软件干扰仍需 T-302/T-601 的目标 Windows VM/真机故障注入;T-302 继续后置到签名向量整改完成 +- 当前 blocker:无;T-613 已建立 payload、staging tree、journal 与目录 rename/remove 的 fail-closed 耐久顺序,并由 Windows 原生目录 `FlushFileBuffers` 用例验证。T-614 将冻结 Catalog canonicalization/签名静态跨实现向量与 surrogate/Base64/`-0` 拒绝边界;完成前 T-302 继续后置。物理断电、文件锁/杀毒软件干扰仍需 T-302/T-601 的目标 Windows VM/真机故障注入 ## 当前目录要点 | 路径 | 状态 | 说明 | | --- | --- | --- | | `docs/` | 已有 | harness coding 文档集(本次初始化完成) | -| `docs/tasks/` | 已有 | Phase 0~2、T-301 与 T-604~T-613 已完成;下一项先落成 Phase 1 签名向量整改,T-302 暂后置 | +| `docs/tasks/` | 已有 | Phase 0~2、T-301 与 T-604~T-613 已完成;T-614 是当前唯一 TODO 的 Phase 1 签名向量整改,T-302 暂后置 | | `scripts/` | 已有 | harness 治理、core 边界、Go 版本检查与 Phase 0 双平台验证入口 | | `core/` | 已建 | Go 1.20 兼容;已有正式 Catalog、本地状态/存储、共享 Windows safepath、列表模型、有界并发图标缓存、图标事件/relay、可恢复下载队列与 Phase 1 安装安全原型 | | `app-modern/` | 已建 | Go 1.25.0 + Gio v0.10.1;Modern AppShell 已接入虚拟列表、详情、图标事件 drain/过期拒绝和内存 ImageOp,并拆为五类 shell 职责文件 | @@ -42,7 +42,7 @@ - 已完成:Phase 0 的 `T-001`~`T-004`;Phase 1 的 `T-101`、`T-102`、`T-103`;Phase 2 的 `T-201`~`T-204`;Phase 3 的 `T-301`;审核整改 `T-604`~`T-613`。 - 正在进行:无。 -- 下一个可领取任务:暂无;应先按 Phase 1 审核定稿顺序落成独立 canonicalization/签名测试向量任务,再领取。T-302/T-601 的物理断电与干扰故障注入仍保留为发布前环境验证。 +- 下一个可领取任务:`T-614` — 冻结 Catalog canonicalization/签名静态跨实现向量,收紧 surrogate、`-0` 与 Base64 文本边界;完成后才可领取 T-302。T-302/T-601 的物理断电与干扰故障注入仍保留为发布前环境验证。 ## 当前可运行内容 diff --git a/docs/review/phase1-security-review.md b/docs/review/phase1-security-review.md index 13a759b..fe1b006 100644 --- a/docs/review/phase1-security-review.md +++ b/docs/review/phase1-security-review.md @@ -202,7 +202,7 @@ ZIP mode 决定 `0o600`/`0o700` 在目标 Windows 上基本不构成安全问题 1. **[阻断 T-302 · 最高 · T-605]** 修 Windows 尾部空格/点、DOS 设备名与路径别名:建逐段 Windows 安全路径校验器(各层共用)+ 提取时 destination 包含性兜底检查;补 Win7/10/11 真实文件系统用例。关闭前不得把 Extractor 描述为"无路径穿越"。 2. **[已关闭 · T-612]** 已在构造 `zip.Reader` 前增加同句柄包大小与中央目录/EOCD(含 ZIP64)预扫描边界;Extractor 强制接收 expected Catalog `size`,核对打开文件长度并限制原始包、中央目录与声明条目数,不只依赖 `len(archive.File)`。T-302 仍须把已验签 Catalog、完成下载文件和 SHA-256 编排为真实安装调用链。 3. **[代码顺序已关闭 · T-613;发布前环境验证仍必做]** payload 文件 Sync/Close → staging 子目录、根与父目录元数据栅栏 → journal 临时文件 Sync/Close 与 root 栅栏 → 每次 rename 后 root 栅栏 → committed 耐久后才删 backup/journal。Windows 已使用 Win7 可用的目录句柄 `CreateFile(FILE_FLAG_BACKUP_SEMANTICS)` + `FlushFileBuffers`,失败 fail closed;fake 覆盖 payload/tree/journal/rename/rollback/recovery/cleanup 失败,且原生 Windows 用例验证目录 fence 可执行。T-302/T-601 仍必须在 VM/真机进行真实断电、文件锁/杀毒干扰注入,不能以单元测试替代。 -4. **[协议冻结前]** `softbox-catalog` 与客户端独立 canonicalization/签名测试向量:Unicode 键序、转义、`<>&`、U+2028/2029、合法/非法 surrogate 拒绝策略、`-0`/大整数/嵌套 signature、base64 padding/CR/LF、同语义不同表示同签名字节。 +4. **[协议冻结前 · T-614]** `softbox-catalog` 与客户端独立 canonicalization/签名测试向量:Unicode 键序、转义、`<>&`、U+2028/2029、合法/非法 surrogate 拒绝策略、`-0`/大整数/嵌套 signature、base64 padding/CR/LF、同语义不同表示同签名字节。T-614 先在本仓库冻结 corpus 和客户端拒绝规则;外部发布端消费同一 corpus 的 CI 证据仍须单独取得。 5. **[整合时复核]** 按真实包采样调解压硬上限,保留硬边界,不降级为软信号。 6. **[实现时重点审]** T-402/T-403 真实健康检查:防"进程短暂启动即判健康"、错误工作目录/错误二进制被探活。 7. **[低优先]** 固定解压落盘 mode,可执行入口由已验证 app.json/Catalog 定义。 diff --git a/docs/tasks/T-614.md b/docs/tasks/T-614.md new file mode 100644 index 0000000..7da9c89 --- /dev/null +++ b/docs/tasks/T-614.md @@ -0,0 +1,71 @@ +--- +id: T-614 +title: 冻结 Catalog 规范化与签名跨实现测试向量 +phase: 1 +deps: [T-613] +status: TODO +created: 2026-07-18 +issue: null +context_ref: null +claim_branch: null +work_branch: null +write_paths: + - docs/tasks/T-614.md + - core/catalog/ + - testdata/catalog/ + - testdata/README.md + - docs/api.md + - docs/04-architecture.md + - docs/05-coding-rules.md + - docs/review/phase1-security-review.md + - docs/00-ai-start-here.md + - docs/06-tasks.md + - docs/current-state.md +--- + +## 问题 / 背景 + +T-101 的 Catalog verifier 已能拒绝重复字段、尾随 JSON 和非整数数字,但它的合法签名测试由同一个客户端 `parseRestrictedJSON` / `canonicalJSON` 在测试期生成。这只能证明客户端实现自洽,不能发现发布端 `softbox-catalog` 与客户端对 Unicode、数字或 Base64 文本规则理解不同的问题。Phase 1 审核定稿的下一项要求在协议冻结前建立独立、固定的 canonicalization/签名测试向量。 + +现有实现也存在两处未冻结的歧义:Go JSON decoder 会把孤立 `\uD800` / `\uDC00` 替换为 U+FFFD,而 `base64.StdEncoding.Strict()` 仍接受 CR/LF。若不在解析和签名域边界显式拒绝,不同发布实现可能对相同文本产生不同签名字节或接受非唯一签名表示。`-0` 也必须明确处理,不能作为与 `0` 语义相同而字节不同的受限整数形式留在协议中。 + +本仓库不包含 `softbox-catalog` 发布端或生产私钥。因此本任务交付可由独立发布端消费的版本化 testdata corpus、公开测试公钥、固定 canonical bytes 与固定 Ed25519 签名;不把客户端运行时 canonicalizer 当成向量的生成器,也不宣称已经替外部发布端执行了集成测试。 + +## 方案 + +1. 在 `docs/api.md`、架构和编码规则冻结 Catalog 签名域的精确边界: + - JSON 字符串必须形成合法 Unicode scalar sequence;`\u` 高代理项必须紧跟低代理项,孤立低代理项或不完整/不匹配 pair 一律拒绝,不得替换为 U+FFFD。 + - 数字只接受 `0`、正整数或负的非零整数;拒绝 `-0`、小数、指数、前导零和超出 JSON token 的其他表示。大整数按原始十进制 token 写入 canonical bytes,不转浮点。 + - 顶层及 package 签名只接受标准 padded Base64 的唯一文本形式:解码后重新编码必须与输入逐字节相等,故 CR/LF、其他空白、缺/多 padding 一律拒绝。 +2. 在 `core/catalog` 以共享的受限 JSON/签名辅助路径落实上述规则,不引入第三方 canonicalization、JSON 或 crypto 依赖。保留“只删除顶层 `signature`,嵌套 package `signature` 仍属于被签名 payload”的已有语义。 +3. 在 `testdata/catalog/` 新建版本化静态 vector corpus 和说明,每个 vector 固定保存原始 document、预期 canonical signing bytes、测试公钥和 Ed25519 signature 或确定的拒绝分类。有效向量至少覆盖 Unicode 键排序、JSON 转义、`<>&`、U+2028/U+2029、合法 surrogate pair、`-0` 拒绝、大整数、嵌套 signature、标准 padding 和“同一语义的不同 JSON 表示得到相同 signing bytes”;拒绝向量至少覆盖孤立 high/low surrogate、非成对 surrogate、Base64 CR/LF/其他空白和 padding 异常。 +4. 测试只读取 corpus 的静态 `signed_payload` / `signature` 作为预期值:先断言客户端输出的 canonical bytes 完全相等,再用 corpus 公钥验签。不得调用 `signCatalogPayload`、`canonicalJSON` 或测试期私钥去生成合法向量的期望值。现有 unit tests 可继续用于局部行为,但 corpus 必须是跨实现契约回归门。 +5. 同步审核记录、路线图和当前状态。明确 T-614 关闭的是客户端协议契约与可交付的独立输入/输出向量;外部 `softbox-catalog` 必须在其仓库消费同一 corpus 并由发布流程证明通过,该外部动作不在本任务内。 + +## 验收要点 + +- `docs/api.md` 对 Unicode surrogate、整数 `-0`、大整数写法、Base64 padding/空白和仅顶层 signature 删除行为没有歧义;架构、编码规则与 corpus README 一致。 +- `testdata/catalog/` 存在可机器读取、版本化的固定 corpus,只含虚构公开测试公钥与固定数据,不含生产私钥、真实 URL 或真实签名。有效 vector 的 expected signing bytes 和 Ed25519 signature 均为静态值;不同语义相同的 document 映射到同一静态 bytes/signature。 +- corpus 覆盖 Unicode 键排序、字符串转义、`<>&`、U+2028/U+2029、合法 surrogate pair、非法 surrogate、`-0`、大整数、嵌套 signature、Base64 padding、CR/LF/其他空白及同语义不同 JSON 表示。 +- Verifier 与 Parser 对孤立/不匹配 surrogate、`-0`、非唯一或非法 signature Base64 fail closed;嵌套 package signature 未被误删;合法向量在不重新签名的情况下通过。 +- corpus 测试的 expected canonical bytes/signature 不由当前 canonicalizer 或运行时私钥生成;测试通过静态 public key 与固定向量验证,从而能发现客户端与发布端的字节级漂移。 +- `GOWORK=off go -C core vet ./catalog`、`GOWORK=off go -C core test -count=10 ./catalog`、`./scripts/verify_phase0.ps1`、`python scripts/validate_agent_context.py`、`python scripts/validate_harness_governance.py` 与提交前差异检查通过。 + +## 边界(不改什么) + +- 不实现或伪造外部 `softbox-catalog` 发布器、生产密钥管理、密钥 ID/轮换、package 独立签名域、HTTPS 请求、Catalog UI 或 T-302 安装整合。 +- 不更改 Catalog manifest/app/package Schema、字段、channel/filter 语义、缓存策略、下载队列、ZIP 安装、Gio 或 Go/Gio 工具链。 +- 不引入第三方 JSON canonicalization/crypto 库,不为了兼容旧的非规范签名文本而放宽拒绝规则。 +- 不把 corpus 的客户端测试表述为外部发布端已消费或全链路发布证明;外部仓库接入和 CI 证据需另行协调。 +- 不修改、提交或删除用户的 `soft_quay.code-workspace`,不启动子 Agent,不提前落成或领取 T-302。 + +## 协作约束 + +- 按仓库当前规则由单 Agent 串行执行,不启动子 Agent。 +- 本任务只允许修改 frontmatter 中的 `write_paths`;若需要外部发布仓库提交、生产密钥、真实发布签名或协议字段/密钥轮换,必须停止并另立任务或请求外部协调。 +- T-614 完成、完整验证并提交前,不落成或领取 T-302;T-302/T-601 的 Windows VM/真机断电、文件锁/杀毒干扰验证仍不因本任务被省略。 + +## 执行记录 + +- 2026-07-18:根据 `docs/review/phase1-security-review.md` 最终处理顺序第 4 项落成;T-613 已完成,因此 T-614 成为下一项唯一任务。 +- 2026-07-18:前置检索确认现有合法签名测试由同一客户端 canonicalizer/测试私钥生成,Go JSON decoder 会将非法 surrogate 替换为 U+FFFD,且标准库 strict Base64 仍容忍 CR/LF。任务据此冻结拒绝策略和静态跨实现 corpus,不把客户端自举测试误作发布端互操作证明。