Define unsafe icon cache diagnostics task (T-611)

This commit is contained in:
ila
2026-07-18 14:31:53 +08:00
parent e9386d26e7
commit 6455fec811
5 changed files with 102 additions and 8 deletions
+2 -2
View File
@@ -45,7 +45,7 @@ SoftBox 软件盒子是一个使用 Go + Gio 开发的 Windows 桌面客户端,
## 当前阶段
当前项目已完成 Phase 0~2、T-301 与审核整改 `T-604`~`T-610`。Windows 安全路径阻断项、图标缓存资源边界、后台结果回 UI 线程的事件接线、双适配器交互契约、`VisibleItems` 快照生命周期和双端 Gio shell 职责拆分均已关闭;下一步按 Phase 2 交叉审核顺序落成 unsafe cache 上层诊断/人工清理指引任务,其余 Phase 1 整改继续串行处理,T-302 暂后置。
当前项目已完成 Phase 0~2、T-301 与审核整改 `T-604`~`T-610`。Windows 安全路径阻断项、图标缓存资源边界、后台结果回 UI 线程的事件接线、双适配器交互契约、`VisibleItems` 快照生命周期和双端 Gio shell 职责拆分均已关闭;`T-611` 已落成,下一步执行 unsafe cache 真实错误贯通、双端安全提示与人工恢复指引,其余 Phase 1 整改继续串行处理,T-302 暂后置。
优先路径:
@@ -53,7 +53,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-610:图标缓存资源边界、UI 线程事件接线、双 Gio 适配器交互契约、`VisibleItems` generation 生命周期和双端 `shell.go` 同 package 镜像职责拆分;下一步落成 unsafe cache 上层诊断/人工清理指引任务,再串行处理其余整改与 T-302/T-303、Phase 4-6。
5. 已完成 T-606~T-610:图标缓存资源边界、UI 线程事件接线、双 Gio 适配器交互契约、`VisibleItems` generation 生命周期和双端 `shell.go` 同 package 镜像职责拆分;下一步执行已落成的 T-611 unsafe cache 诊断/人工恢复指引,再串行处理其余整改与 T-302/T-303、Phase 4-6。
## 领取任务规则
+2 -1
View File
@@ -59,7 +59,7 @@ Phase 1 安全整改按 `docs/review/phase1-security-review.md` 的交叉复核
#### Phase 2 交叉审核加固
Phase 2 整改按 `docs/review/phase2-review.md` 的交叉复核定稿顺序串行落成。T-606 已关闭正式图标接入前的并发、读取和内存边界;T-607 建立后台图标结果经 application event 回到 Gio UI goroutine 的线程契约;T-608 为两个隔离 Gio 适配器建立交互契约;T-609 修正 `VisibleItems` 快照生命周期;T-610 在不改变行为的前提下拆分双端 Gio shell 职责。
Phase 2 整改按 `docs/review/phase2-review.md` 的交叉复核定稿顺序串行落成。T-606 已关闭正式图标接入前的并发、读取和内存边界;T-607 建立后台图标结果经 application event 回到 Gio UI goroutine 的线程契约;T-608 为两个隔离 Gio 适配器建立交互契约;T-609 修正 `VisibleItems` 快照生命周期;T-610 在不改变行为的前提下拆分双端 Gio shell 职责;T-611 为不安全缓存补可定位诊断与人工恢复指引。
| ID | 任务 | 依赖 | 验收要点 |
| --- | --- | --- | --- |
@@ -68,6 +68,7 @@ Phase 2 整改按 `docs/review/phase2-review.md` 的交叉复核定稿顺序串
| T-608 | 建立双 Gio 适配器交互契约 | T-607 | 双端同场景验证 Editor/Clickable 接线、AppID 行身份、详情上下文、500 项 viewport 与控件释放;不重复 ViewModel 纯逻辑 |
| T-609 | 修正 VisibleItems 快照生命周期 | T-608 | refilter 构造新 backing array 后替换;旧快照跨 model 更新保持稳定;每帧读取不复制;明确单 owner 与只读约定 |
| T-610 | 拆分双端 Gio shell 职责 | T-609 | 两端按状态/根编排、header/navigation、catalog/list、detail、style 拆为同 package 镜像文件;行为、事件顺序、视觉与 Gio 隔离不变 |
| T-611 | 建立不安全图标缓存诊断与人工恢复指引 | T-610 | 真实非普通 entry 贯通 unsafe_cache event;双端详情显示无敏感字段的 fail-closed/人工处理提示;不自动删除或 quarantine |
### Phase 3 · 下载与安装
+4 -4
View File
@@ -13,7 +13,7 @@
## 当前快照
- 日期:2026-07-18
- 阶段:Phase 2 已完成(T-201~T-204);Phase 3 的 T-301 可恢复下载队列已完成;审核整改 T-604~T-610 已完成,T-302 继续暂后置
- 阶段:Phase 2 已完成(T-201~T-204);Phase 3 的 T-301 可恢复下载队列已完成;审核整改 T-604~T-610 已完成,T-611 已落成待执行,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 解压/回滚原型、发布稳定只读 generation 的无 IO 软件列表模型、按 key in-flight + 流式有界读取 + 32 MiB/256-key LRU 的可信图标缓存、图标 Load/Decode 事件发布用例、有界 application event relay,以及默认并发 2 的持久可恢复下载队列;modern/win7 主循环已接 relay/Invalidate,AppShell 已实现搜索/分类/视图、惰性列表、详情右栏、图标请求身份与 UI-only ApplyEvent/ApplyIcon,并按 root/header/catalog/detail/style 同 package 镜像职责拆文件
- 测试:core 覆盖 Catalog、列表快照 generation/零复制、SemVer/12 状态、本地安装记录、Windows dot-space/设备名/Unicode 折叠路径攻击、ZIP destination 包含性、图标并发/取消/读取边界/LRU、图标事件身份/失败分类/relay 背压与关闭、下载并发/暂停/取消/重试/Range/断连/恢复/事件失败与文件身份替换;两个 app 覆盖 Editor/视图/分类/行/恢复/关闭接线、500 项 viewport、AppID 控件与分类控件生命周期、详情上下文、空状态语义、UI drain 前后、最新/取消/换引用图标结果与平台 stub;安装恢复矩阵保持通过
@@ -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:无;下一步按 `docs/review/phase2-review.md` 最终顺序落成 unsafe cache 上层诊断/人工清理指引任务,自动 quarantine 另做威胁模型;Phase 1 中央目录预扫描等继续串行,T-302 继续后置
- 当前 blocker:无;下一步领取并执行 `T-611`,用真实 cache→event 测试、双端可见提示和人工 runbook 闭合 unsafe cache 诊断;自动 quarantine 另做威胁模型,Phase 1 后续整改与 T-302 继续后置
## 当前目录要点
| 路径 | 状态 | 说明 |
| --- | --- | --- |
| `docs/` | 已有 | harness coding 文档集(本次初始化完成) |
| `docs/tasks/` | 已有 | Phase 0~2、T-301 与 T-604~T-610 已完成;其余审核整改尚未编号,T-302 暂后置 |
| `docs/tasks/` | 已有 | Phase 0~2、T-301 与 T-604~T-610 已完成;T-611 已落成待执行,其余审核整改尚未编号,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-610`。
- 正在进行:无。
- 下一个可领取任务:无;先按 `docs/review/phase2-review.md` 最终顺序落成 unsafe cache 上层诊断/人工清理指引任务。
- 下一个可领取任务:`T-611`(依赖 `T-610` 已完成),不安全图标缓存诊断与人工恢复指引。
## 当前可运行内容
+1 -1
View File
@@ -288,4 +288,4 @@ modern/win7 的 `ApplyIcon` 都直接写 `shell.icons` map,Layout 同时读取
- `T-608` 已完成最终处理顺序第 3 项:modern/win7 使用除 edition/窗口尺寸外一致的场景矩阵,验证 Editor/Clickable 经 Layout 更新共享 model、重排后 AppID 行身份、详情关闭上下文、500 项 viewport、app/category controls 释放及两类空状态语义;双端定向重复测试与完整闸门通过,生产 `shell.go` 无需修正。
- `T-609` 已完成最终处理顺序第 4 项:`refilter` 在局部新 backing array 完整构造后发布,旧 generation 跨五类公开 model mutation 保持稳定;同 generation 读取共享 backing 且零分配。core 定向重复测试、双 Gio 回归与完整闸门通过,未扩展为并发安全或防御性深拷贝。
- `T-610` 已完成最终处理顺序第 5 项:双端 `shell.go` 只保留 AppShell 状态/生命周期和根编排,其余声明进入 header/navigation、catalog/list、detail、style 同 package 镜像职责文件;原 54 个声明与根调用链保持不变,双端适配器/图标回归及完整闸门通过。
- unsafe cache 诊断尚未编号;下一任务按最终处理顺序第 6 项正式裁定上层诊断与人工清理指引,自动 quarantine 仍需独立威胁模型。
- `T-611` 已按最终处理顺序第 6 项落成待执行:用真实非普通 cache entry 贯通 `ErrIconCacheUnsafe → IconFailed/unsafe_cache`,双端只显示稳定安全字段与人工恢复指引;自动 quarantine 仍需独立威胁模型。
+93
View File
@@ -0,0 +1,93 @@
---
id: T-611
title: 建立不安全图标缓存诊断与人工恢复指引
phase: 2
deps: [T-610]
status: TODO
created: 2026-07-18
issue: null
context_ref: null
claim_branch: null
work_branch: null
write_paths:
- docs/tasks/T-611.md
- core/catalog/
- app-modern/ui/gio/
- app-win7/ui/gio/
- docs/troubleshooting.md
- docs/api.md
- docs/routes.md
- docs/04-architecture.md
- docs/05-coding-rules.md
- docs/review/phase2-review.md
- docs/00-ai-start-here.md
- docs/06-tasks.md
- docs/current-state.md
---
## 问题 / 背景
`core/catalog.IconCache` 已对磁盘缓存执行 `os.Lstat`:entry 是 symlink 或非普通文件时返回 `ErrIconCacheUnsafe`,`loadUncached` 立即 fail closed,不删除该 entry、也不回退远端。`IconEventDelivery` 已把该错误归类为稳定且不含敏感信息的 `application.IconFailureUnsafe` (`unsafe_cache`),`IconFailed` 身份中已有 request ID、app ID、规范 icon reference 与 DPI。这个安全决策本身合理,不应改成自动修复。
当前缺口位于上层诊断:modern/win7 的 `ApplyEvent` 只把 failure code 保存到 map,`IconFailure` 也只有测试调用;列表/详情没有显示 `unsafe_cache`,用户或运维无法区分普通离线占位与被安全策略阻断。缓存层也没有从真实非普通 entry 一直贯通到 `IconFailed` 的集成测试。与此同时 `IconCache.Load`/`IconEventDelivery.LoadAndPublish` 仍无生产调用者,因此不能把单元测试契约描述成已经接入真实日志或网络图标装配。
本任务关闭最终审核中的观察项:用现有稳定事件字段形成可定位但不泄密的诊断,在双端详情显示明确的 fail-closed 与人工恢复指引,并增加运维 runbook。它不自动删除、rename 或 quarantine 可疑对象,也不顺带接入生产 Fetcher。
## 方案
1. 用真实缓存文件系统条件固化 fail-closed:
- 在 `core/catalog` 创建请求对应路径为目录或其他确定的非普通 entry,调用真实 `IconCache.Load`,断言返回链可 `errors.Is(ErrIconCacheUnsafe)`、Fetcher 零调用、entry 未删除/改写且未发生远端回退。
- 在平台允许创建 symlink 时补同样矩阵并验证外部 target 未读取/改写;权限不允许时可以明确 skip,但跨平台非普通 entry 用例必须执行。
- 用真实 `IconCache` 作为 `IconEventDelivery.Loader`,证明同一错误发布 `IconFailed/unsafe_cache`,同时后台返回值仍保留 `ErrIconCacheUnsafe`;event payload 不包含原始 error、绝对路径、URL/query 或 symlink target。
2. modern 与 Win7 使用相同的本地 failure state 保存最近一次已验证失败的完整 `IconEventIdentity` 与 `IconFailureCode`,而不是只保存 code:
- 保持现有 `IconFailure(appID)` 查询签名兼容;详情布局直接读取同 package 状态。
- 只有 request ID、app ID、icon reference 与 DPI 都匹配当前最新请求时才能写入诊断。
- `ExpectIcon` 新 generation、匹配的 `IconReady`/`ApplyIcon`、IconRef 变化、取消和 app 删除按现有生命周期清除诊断;同引用 Catalog 更新可保留当前 failure。
3. 仅对 `IconFailureUnsafe` 在选中软件详情中显示持久、可访问的安全提示:
- 明确“缓存项未被使用、本次请求没有继续远端获取或自动修复”,提示退出 SoftBox 后按故障排查文档人工处理。
- 显示稳定诊断码、app ID 与由规范 digest + DPI 组成的缓存 locator;不得显示原始 URL/query、后台 error、symlink target 或未经验证的路径文本。
- 不增加“自动清理/重试/quarantine”按钮;`unavailable`/`invalid_content` 继续使用现有占位行为,避免把普通离线或坏内容误报为安全事件。
- 提示位于 `shell_detail.go`,modern/Win7 保持相同语义和可访问文本,视觉可遵循各自 Gio 版本既有样式。
4. 扩展双端 UI 回归:
- 注入当前请求的 `unsafe_cache` event,打开对应详情,通过 Gio 语义树断言安全提示、code/app/locator 可见且没有敏感原始错误。
- 覆盖迟到/换引用/换 DPI/取消/删除 app 不产生提示,新请求或成功 ready 清除旧提示,同引用 `SetItems` 保留诊断。
- 保持 T-607 最新请求/过期拒绝和 T-608 详情上下文/虚拟化契约通过。
5. 新增 `docs/troubleshooting.md` 的“不安全图标缓存”人工恢复 runbook:
- 解释诊断字段,把 locator 映射为 `<configured-icon-cache-root>/<digest>-<dpi>.icon`;安装模式正式装配的目标根为 `%LOCALAPPDATA%/OwnSoftBox/cache/icons/`,但在生产装配完成前明确以传给 `NewIconCache` 的 root 为事实,不虚构当前 cmd 已使用该路径。
- 要求先完全退出 SoftBox,确认 cache root 位于预期数据根且 root 本身不是 reparse point,不要打开/跟随可疑 entry 的 target;由管理员在根的父目录侧人工移走已核验的图标缓存目录或处理单个 entry,保留诊断信息后再启动重建。
- 明确普通用户拿不准时停止并联系管理员;不提供递归跟随链接的删除命令,不把人工步骤包装成应用内自动操作。
6. 同步 API、架构、路由和编码规则,冻结 `unsafe_cache` 的稳定字段、UI 语义、runbook 入口与“生产 IconCache/Fetcher 装配仍待后续任务”的真实状态。
## 验收要点
- 真实目录/非普通 cache entry 使 `IconCache.Load` 返回 `ErrIconCacheUnsafe`,Fetcher 不调用、entry 不删除/改写、无远端回退;可创建 symlink 的环境同时证明外部 target 不受影响。
- 真实 cache → delivery 路径发布唯一 `IconFailed` 且 code 为 `unsafe_cache`;后台返回错误仍可 `errors.Is(ErrIconCacheUnsafe)`,取消语义不变。
- application event/UI 不携带或显示原始 error、绝对 cache root、URL/query、token 或 link target;可定位字段仅含稳定 code、request/app 身份、规范 SHA-256 reference 与 DPI。
- modern/Win7 对当前 selected app 的 unsafe failure 显示等价、可访问的 fail-closed 与人工处理提示;普通 unavailable/invalid 不冒充安全告警。
- 最新请求、换 reference/DPI、取消、删除、ready 与同引用 snapshot 更新的诊断状态生命周期有双端测试;现有 `IconFailure(appID)` API 保持兼容。
- `docs/troubleshooting.md` 明确 locator→文件名、目标 cache root、停进程/不跟随 target/管理员处理/重启验证步骤,并如实标注当前 production IconCache/Delivery 尚未装配。
- 不新增自动 delete/rename/quarantine/retry 代码,不让不安全 entry 触发网络回退,不降低现有 fail-closed 判断。
- Go 1.20.14 + `GOWORK=off` 下 `go vet ./catalog`、`go test -count=10 ./catalog` 通过;modern Go 1.25.0 与 Win7 Go 1.20.14 下 `go test -count=10 ./ui/gio` 分别通过。
- 完整 `./scripts/verify_phase0.ps1`、`python scripts/validate_agent_context.py`、`python scripts/validate_harness_governance.py` 与提交前差异检查通过。
## 边界(不改什么)
- 不接入真实 HTTP IconFetcher、CDN 映射、生产 `NewIconCache`/`LoadAndPublish` 调用方或后台任务调度;当前无生产调用者的事实必须保留在文档。
- 不把 raw error、绝对路径、URL/query、token 或 symlink target 加入 application event/持久状态/UI;不建立遥测或上传诊断。
- 不自动删除、rename、隔离或修复 symlink/reparse point/非普通 entry,不新增一键清理按钮;自动 quarantine 仍需独立威胁模型与 Windows 攻击测试。
- 不修改图标并发、LRU、大小/尺寸校验、磁盘命名、Fetcher 流式上限或 Gio UI 线程事件投递契约。
- 不把普通损坏文件、离线、hash/decode 失败提升为 `unsafe_cache`;现有损坏普通文件删除重取策略保持不变。
- 不修改下载/安装/授权流程、协议 Schema、Go/Gio 版本、workspace 或 T-302;不修改、提交或删除用户的 `soft_quay.code-workspace`。
## 协作约束
- 按仓库当前规则由单 Agent 串行执行,不启动子 Agent。
- 本任务只允许修改 frontmatter 中的 `write_paths`;若实现需要生产网络装配、自动文件处置或新增事件敏感字段,必须停止并另立任务,不得在 T-611 内扩边。
- T-611 完成、完整验证并提交前,不落成或领取 Phase 1 后续整改或 T-302。
## 执行记录
- 2026-07-18:根据 `docs/review/phase2-review.md` 最终处理顺序第 6 项落成任务;现有全局最大任务为 T-610,因此取 T-611,依赖已完成的 T-610。
- 2026-07-18:代码图确认 `loadDisk` 已拒绝 symlink/非普通 entry,`loadUncached` 遇 `ErrIconCacheUnsafe` 直接返回且不 fetch;`classifyIconFailure` 已映射为 `unsafe_cache`,`IconFailedPayload` 已含安全的 reference/DPI,无需扩展 raw error 字段。
- 2026-07-18:调用图确认 `IconEventDelivery.LoadAndPublish` 与双端 `IconFailure` 目前都只有测试调用;双端 `ApplyEvent` 只保存 failure code 且布局不读取。任务因此采用“真实 cache→event 集成测试 + UI 安全提示 + 人工 runbook”,明确不宣称生产装配已完成。
- 2026-07-18:现有 `docs/api.md` 已冻结磁盘名 `<digest>-<dpi>.icon` 与 `unsafe_cache` 稳定枚举,架构只定义 `%LOCALAPPDATA%/OwnSoftBox/cache/` 逻辑根;T-611 将细化正式 icon root 与 locator 映射,同时保留 constructor root 才是当前代码事实。