docs: design N15 selector report hardening
This commit is contained in:
@@ -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。
|
||||
Reference in New Issue
Block a user