From 2609b2028df84227c58e07d87c81db14e5084748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9F=B3=E5=A4=B4?= <3301352378@qq.com> Date: Thu, 9 Jul 2026 17:04:19 +0800 Subject: [PATCH] docs: design N15 selector report hardening --- ...09-n15-selector-report-hardening-design.md | 266 ++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-09-n15-selector-report-hardening-design.md diff --git a/docs/superpowers/specs/2026-07-09-n15-selector-report-hardening-design.md b/docs/superpowers/specs/2026-07-09-n15-selector-report-hardening-design.md new file mode 100644 index 0000000..9bbe28e --- /dev/null +++ b/docs/superpowers/specs/2026-07-09-n15-selector-report-hardening-design.md @@ -0,0 +1,266 @@ +# N15 Selector Report Hardening Design + +日期:2026-07-09 + +## 1. 背景 + +N14 已在 `main` 上合并并推送,提供了离线 UIA selector 报告能力: + +- `internal/uiaselector/report.go`:报告模型、catalog report generator、JSON/Markdown renderer。 +- `cmd/uia-selector-report/main.go`:离线 CLI,只接受 dump 文件路径和输出报告路径。 +- `scripts/verify-n14-uia-selector-report.ps1`:运行测试、生成报告并做基础安全扫描。 + +N14 最终 review 留下两个非阻塞建议: + +1. `StrictExitAmbiguous` 分支缺少显式单测。 +2. verifier 的输出安全检查仍以字符串扫描为主,并且运行 CLI 前没有清理旧输出文件。 + +N15 的目标是把这些 review 建议收口为一个小型加固节点,不扩展产品能力,不接触 live UI,不新增 helper op,不新增 MCP tool,不扩展 live action surface。 + +## 2. 目标 + +N15 只做报告安全与验证加固: + +1. 增加 `StrictExitAmbiguous` 显式单测,锁定 `ambiguous_count > 0` 时严格模式返回 `3`。 +2. 加固 `scripts/verify-n14-uia-selector-report.ps1`,避免旧报告掩盖 CLI 输出回归。 +3. 增加输出文件检查:必须存在、必须是文件、大小必须大于 0。 +4. 增加 JSON 字段 allowlist 检查,确认 `selectors[].matches[]` 只包含允许的结构字段。 +5. 保持 N14 报告格式和 CLI 参数不变。 + +## 3. 非目标 + +N15 不做以下事项: + +```text +新增 selector +修改 selector catalog +修改报告 JSON/Markdown 格式 +新增 CLI 参数 +新增 helper op +新增 MCP tool +连接内网 iSphere +live dump 当前窗口 +使用 hwnd/process 参数 +点击控件 +输入文本 +打开会话 +搜索联系人 +发送消息 +发送文件 +接收文件 +读取聊天内容 +导出完整 visible text +``` + +N15 也不清理本地分支。分支清理可以在 N15 合并后单独执行。 + +## 4. 推荐方案 + +采用“小范围加固”方案,只修改两个文件: + +```text +internal/uiaselector/report_test.go +scripts/verify-n14-uia-selector-report.ps1 +``` + +不修改: + +```text +native/ISphereWinHelper/Program.cs +internal/tools/winhelper.go +cmd/isphere-mcp/main.go +cmd/uia-selector-report/main.go +internal/uiaselector/report.go +``` + +理由:N14 的实现逻辑已经通过最终验证和 review。N15 只需要补测试和 verifier 防回归能力,不需要改变生产代码路径。 + +## 5. Ambiguous strict-mode 测试设计 + +在 `internal/uiaselector/report_test.go` 中新增测试: + +```text +TestStrictModeClassifiesAmbiguousMatches +``` + +测试构造一个最小 UIA tree: + +```text +root Window frmMain + child Pane duplicate + child Pane duplicate +``` + +测试 catalog 只包含一个 read-only selector: + +```text +id: duplicate_panel +allowed_use: read_only_locate +required: + automation_id: duplicate + control_type: Pane + framework_id: WinForm +``` + +期望: + +```text +report.OK == true +report.CatalogSize == 1 +report.MatchedCount == 1 +report.UnmatchedCount == 0 +report.AmbiguousCount == 1 +report.Selectors[0].MatchCount == 2 +StrictExitCode(report) == StrictExitAmbiguous +``` + +该测试只使用内存构造的 `Node`,不依赖 N12R fixture,也不涉及 visible text。 + +## 6. Verifier 加固设计 + +### 6.1 清理旧输出 + +在 `go run ./cmd/uia-selector-report` 之前删除旧报告: + +```powershell +Remove-Item -LiteralPath $jsonPath, $mdPath -Force -ErrorAction SilentlyContinue +``` + +目的:如果未来 CLI 回归为“退出 0 但未写新文件”,旧报告不会让后续存在性检查误通过。 + +### 6.2 文件存在和非空检查 + +替换当前 `Test-Path` 检查为更严格的文件检查: + +```powershell +$jsonItem = Get-Item -LiteralPath $jsonPath -ErrorAction Stop +if (-not $jsonItem.PSIsContainer -eq $false) { ... } +if ($jsonItem.Length -le 0) { ... } +``` + +实际实现应避免 PowerShell 运算优先级歧义,推荐使用清晰表达: + +```powershell +if ($jsonItem.PSIsContainer) { throw "JSON report path is not a file: $jsonPath" } +if ($jsonItem.Length -le 0) { throw "JSON report is empty: $jsonPath" } +``` + +Markdown 报告同样检查。 + +### 6.3 JSON match 字段 allowlist + +解析 JSON 后,对每个 `selectors[].matches[]` 对象检查字段集合,只允许: + +```text +path +control_type +automation_id +class_name +framework_id +has_name +name_length +``` + +禁止出现: + +```text +name +value +text +visible_text +``` + +实现策略: + +1. 遍历 `$report.selectors`。 +2. 遍历 `$selector.matches`。 +3. 对每个 match 的 `$match.PSObject.Properties.Name` 做集合比较。 +4. 遇到不在 allowlist 中的字段立即失败。 +5. 如果字段名是 `name` 或 `value`,错误信息明确指出这是 visible text 字段泄漏。 + +这比单纯扫描字符串更稳,因为它检查的是 JSON 对象结构。 + +### 6.4 保留字符串扫描 + +保留 N14 已有字符串扫描,用于发现: + +```text +"name" +"value" +国网甘肃省电力公司 +send_after_approval +send_file_after_approval +receive_file_after_approval +open_conversation +InvokePattern +ValuePattern.SetValue +``` + +字段 allowlist 和字符串扫描同时存在: + +- allowlist 负责结构化字段泄漏。 +- 字符串扫描负责已知敏感文本和动作词防线。 + +## 7. 测试与验证策略 + +N15 implementation plan 必须采用 TDD。 + +最小验证命令: + +```powershell +go test ./internal/uiaselector -run "TestStrictModeClassifiesAmbiguousMatches" -count=1 +powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-n14-uia-selector-report.ps1 +go test ./... +``` + +最终验证命令: + +```powershell +go test ./internal/uiaselector ./cmd/uia-selector-report -count=1 +go test ./... +powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-n14-uia-selector-report.ps1 +git diff --name-only main..HEAD -- native/ISphereWinHelper/Program.cs internal/tools/winhelper.go cmd/isphere-mcp/main.go +``` + +最后一条命令期望无输出。 + +## 8. 风险与缓解 + +### 风险 1:PowerShell 对单元素数组展开造成遍历差异 + +缓解:实现遍历时使用 `@($report.selectors)` 和 `@($selector.matches)`,确保单元素和多元素都按数组处理。 + +### 风险 2:allowlist 检查误报 PowerShell 内置属性 + +缓解:只读取 `$match.PSObject.Properties.Name`,这返回 JSON 对象自身属性,不读取方法或扩展成员。 + +### 风险 3:旧输出清理误删非目标文件 + +缓解:只删除两个明确计算出的文件路径: + +```text +runs/n14-selector-report/n12r-selector-report.json +runs/n14-selector-report/n12r-selector-report.md +``` + +不递归删除目录。 + +## 9. 验收条件 + +N15 可接受条件: + +1. `StrictExitAmbiguous` 有显式单测。 +2. verifier 运行 CLI 前删除旧 JSON/Markdown 输出文件。 +3. verifier 检查 JSON/Markdown 输出存在、是文件、非空。 +4. verifier 对 JSON `matches[]` 执行字段 allowlist 检查。 +5. verifier 继续扫描 `"name"`、`"value"`、已知 visible text 和动作词。 +6. `go test ./...` 通过。 +7. `scripts\verify-n14-uia-selector-report.ps1` 通过。 +8. 不修改 helper/MCP/live action 文件。 + +## 10. 推荐下一步 + +下一步写 N15 implementation plan,拆分为两个小任务: + +1. TDD 增加 ambiguous strict-mode 单测。 +2. 加固 verifier 的旧输出清理、文件检查和 JSON 字段 allowlist。 \ No newline at end of file