Files
zclaw_openfang/docs/features/audit-v12/M3-hands-system.md
iven 442ec0eeef
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
docs(audit): V12 模块化端到端审计报告 — 11 模块 + 总报告
混合矩阵式审计:10 个功能模块 × 五维检查清单
- 项目整体健康度: 76/100
- 2 个 P0 (M4 双数据库 + 反思引擎 LLM 未接入)
- 15 个 P1 (跨 M2/M3/M4/M5/M6/M7/M11)
- 三类断链模式: 写了没接/接了不对/双实现未统一
- 三阶段修复路线图: P0(2-3天) → P1(5-7天) → P2(5-7天)
2026-04-04 17:55:03 +08:00

171 lines
9.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.

# 模块 M3 Hands 自主能力 审计报告
> **审计版本**: V12
> **审计日期**: 2026-04-04
> **审计范围**: 11 个自主能力包触发/审批/执行/回调完整流程
---
## 1. 模块概况
### 功能描述
9 个已实现 HandBrowser/Researcher/Collector/Clip/Twitter/Whiteboard/Slideshow/Speech/Quiz含触发、审批、执行、事件回调全流程。
### 涉及文件清单
**前端**
- Store: `desktop/src/store/handStore.ts`, `desktop/src/store/browserHandStore.ts`
- Client: `desktop/src/lib/kernel-hands.ts`, `desktop/src/lib/browser-client.ts`
- 自主管理: `desktop/src/lib/autonomy-manager.ts`
- UI: `desktop/src/components/Automation/` (ApprovalQueue, AutomationPanel, ExecutionResult 等)
**后端 (Rust)**
- Tauri 命令: `desktop/src-tauri/src/kernel_commands/hand.rs`, `approval.rs`
- Kernel: `crates/zclaw-kernel/src/kernel/hands.rs`, `crates/zclaw-kernel/src/kernel/approvals.rs`
- Registry: `crates/zclaw-hands/src/registry.rs`
- Hand 实现: `crates/zclaw-hands/src/hands/` (9 个 .rs 文件)
- 配置: `hands/*.HAND.toml` (9 个)
### 调用链路图
```
用户触发 Hand
→ AutomationPanel.triggerHand(id, params)
→ handStore.triggerHand()
→ canAutoExecute('hand_trigger', 5) [前端自主性检查]
→ client.triggerHand(name, params, autonomyLevel)
→ KernelClient.triggerHand() → invoke('hand_execute', { id, input, autonomyLevel })
→ Rust hand_execute:
├─ supervised 模式 → 创建 ApprovalEntry → 返回 pending_approval
├─ needs_approval + 非 autonomous → 创建 ApprovalEntry
└─ autonomous 或无需审批 → kernel.execute_hand()
→ HandRegistry.execute() → Hand::execute()
→ 创建 HandRun (Pending→Running→Completed/Failed)
→ emit("hand-execution-complete")
审批流程:
ApprovalQueue → handStore.respondToApproval()
→ invoke('approval_respond', { id, approved, reason })
→ kernel.respond_to_approval()
→ tokio::spawn 异步执行 Hand + 轮询完成
→ emit("hand-execution-complete")
前端事件接收:
kernel-hands.ts onHandExecutionComplete → listen("hand-execution-complete")
→ [问题: 前端未注册全局监听]
```
---
## 2. 五维检查结果
### 2.1 链路完整性
| 链路 | 起点 | 终点 | 状态 | 备注 |
|------|------|------|------|------|
| Hand 列举 | AutomationPanel | Registry.list() | ✅ 连通 | |
| Hand 触发(直接执行) | handStore | Hand::execute() | ⚠️ **run_id 丢失** | hand.rs:184 丢弃 _run_id |
| Hand 触发(需审批) | handStore | ApprovalEntry | ✅ 连通 | |
| 审批确认 | ApprovalQueue | Hand::execute() | ✅ 连通 | 两条重复路径 |
| hand-execution-complete 事件 | Rust emit | 前端 listen | ⚠️ **无人接收** | onHandExecutionComplete 未被调用 |
| Browser Hand (Rust) | BrowserHand::execute() | 浏览器操作 | ❌ **断裂** | 只返回结构化指令不执行 |
| Browser Hand (前端) | browserHandStore | browser-client → Rust browser_* 命令 | ✅ 连通 | 但绕过审批流程 |
| Hand 取消 | handStore | cancel_flag AtomicBool | ✅ 连通 | |
| Hand 运行状态查询 | handStore | Rust hand_run_status | ✅ 连通 | |
### 2.2 参数/类型一致性
| 接口 | 前端参数 | 后端参数 | 一致性 | 备注 |
|------|---------|---------|--------|------|
| hand_execute | `{ id, input, autonomyLevel }` | `id, input, autonomy_level` | ✅ | camelCase 正确转换 |
| hand_approve | `{ handName, runId, approved, reason }` | `hand_name, run_id, approved, reason` | ✅ | |
| approval_respond | `{ id, approved, reason }` | `id, approved, reason` | ✅ | |
| hand_execute 返回值 | 期望 `{ instance_id, status }` | 实际 `{ success, output, error, durationMs }` | ❌ **不匹配** | 前端拿不到 run_id |
| TOML permissions/rate_limit | 定义了但未使用 | HandConfig 只有基础字段 | ❌ **配置未生效** | |
### 2.3 边界与错误处理
| 场景 | 预期行为 | 实际行为 | 级别 |
|------|---------|---------|------|
| 触发不存在的 Hand | 返回错误 | ✅ ZclawError::NotFound | P4 正确 |
| 并发触发同一 Hand | 限流/拒绝 | ❌ 无保护,可无限并发 | **P1** |
| Hand 执行超时 | 超时终止 | ❌ `timeout_secs` 定义但未使用 | **P1** |
| 审批不响应 | 超时清理 | ❌ 永不过期pending 无限积累 | P2 |
| Hand 执行失败 | 错误传播到 UI | ✅ 错误传播链路完整 | P4 正确 |
| max_concurrent 限制 | 限制并发数 | ❌ TOML 定义但未实现 | P2 |
| Clip Hand 路径含单引号 | 正常执行 | ⚠️ FFmpeg concat 文件解析可能异常 | P3 |
### 2.4 状态管理
| Store/组件 | 状态机完整性 | 持久化 | 竞态风险 |
|-----------|------------|--------|---------|
| handStore | ✅ idle/running/needs_approval/error/unavailable/setup_needed | ❌ 无持久化 | ⚠️ triggerHand 后依赖异步 loadHands 更新状态 |
| browserHandStore | ✅ sessions/execution/logs/templates | ❌ 无持久化 | ⚠️ 独立于 handStore绕过审批 |
| autonomy-manager | ✅ 三级自主+三级风险 | ✅ localStorage | ❌ 仅前端,后端无强制 |
### 2.5 安全与资源
| 检查项 | 状态 | 说明 |
|--------|------|------|
| 审批安全(跨 Hand 攻击防护) | ✅ | entry.hand_id == hand_name 校验 |
| Approval ID 不可预测 | ✅ | UUID v4 |
| Browser Hand 绕过审批 | ❌ **P1** | browserHandStore 完全绕过 autonomy-manager |
| TOML requires_approval 形同虚设 | ❌ **P1** | browserHandStore 不走审批流程 |
| 审批内存不释放 | ⚠️ P2 | Vec<ApprovalEntry> 无限增长 |
| Clip Hand 命令注入防护 | ✅ | 使用 Command::new + .args() 非 shell |
---
## 3. 问题清单
| ID | 描述 | 文件:行号 | 级别 | 修复建议 | 验证方法 |
|----|------|----------|------|---------|---------|
| M3-01 | **hand_execute 丢弃 run_id** — 前端拿不到执行 ID无法追踪运行状态 | `hand.rs:184` | **P1** | 将 HandRunId 包含在返回结果中 | triggerHand 后检查返回值含 runId |
| M3-02 | **Browser Hand 双路径断裂** — Rust execute() 只返回结构化指令不执行实际操作 | `browser.rs:191-343` | **P1** | 统一路径:要么 Rust 端执行操作,要么移除 Rust BrowserHand | 通过 handStore 触发 browser 验证 |
| M3-03 | **browserHandStore 绕过审批** — 无视 TOML requires_approval = true | `browserHandStore.ts:222-339` | **P1** | browserHandStore 操作前检查 autonomy-manager | 在 autonomous=false 下执行浏览器操作 |
| M3-04 | **max_concurrent 未实现** — TOML 定义但运行时无检查 | `kernel/hands.rs` | **P1** | 在 execute_hand 中添加并发计数检查 | 并发触发同一 Hand |
| M3-05 | **timeout_secs 未实现** — Hand 可无限挂起 | `hand.rs:47` | **P1** | 在 execute 中使用 tokio::time::timeout 包装 | 启动长时间 Hand 验证超时 |
| M3-06 | hand_execute 返回值类型不匹配 | `kernel-hands.ts:94` vs Rust `HandResult` | **P2** | 统一返回类型定义 | 检查 triggerHand 返回值 |
| M3-07 | hand-execution-complete 事件前端无人监听 | `kernel-hands.ts:195` | **P2** | 应用初始化时注册全局监听 | 审批执行完成后检查 UI 更新 |
| M3-08 | 审批条目永不过期,内存不释放 | `approvals.rs:16-19` | **P2** | 添加 expires_at + 定期清理 | 检查长期运行后的 pending_approvals 大小 |
| M3-09 | 重复审批确认路径hand_approve vs approval_respond | `hand.rs:196-295` vs `approval.rs:54-142` | **P2** | 统一为单一审批路径 | 检查两路径是否行为一致 |
| M3-10 | tool_count/metric_count 硬编码为 0 | `hand.rs:82-83` | **P2** | 从 Hand 实际能力中计算 | 检查 hand_list 返回值 |
| M3-11 | TOML permissions/rate_limit/audit 配置未被读取 | `hands/*.HAND.toml` vs `hand.rs:10-32` | **P3** | 扩展 HandConfig 或在运行时解析 | 修改 TOML 验证行为无变化 |
| M3-12 | hand_trigger 在 autonomy-manager 中永远不自动允许 | `autonomy-manager.ts:268` | **P3** | 修正映射逻辑 | 设为 autonomous 后触发 Hand |
| M3-13 | Clip Hand concat 文件路径单引号未转义 | `clip.rs:448` | **P3** | 转义路径中的单引号 | 使用含单引号的路径执行 concat |
---
## 4. 改进建议
### 短期修复(按优先级)
1. **[P1]** 修复 `hand_execute` 返回 `run_id`:在 HandResult 中添加 `run_id` 字段
2. **[P1]** 统一 Browser Hand 路径:移除 Rust BrowserHand 的假执行,让 handStore 走 browserHandStore
3. **[P1]** browserHandStore 集成审批流程:在 executeTemplate 前检查 autonomy-manager
4. **[P1]** 实现 `max_concurrent` 并发限制
5. **[P1]** 实现 `timeout_secs` 超时保护
### 长期架构建议
- 注册 `hand-execution-complete` 全局监听器到 handStore
- 统一审批路径(移除重复的 hand_approve / approval_respond
- 添加审批 TTL 和自动清理
- 扩展 HandConfig 解析 TOML 中的权限/限流/审计配置
---
## 5. 健康度评分
| 维度 | 评分 | 说明 |
|------|------|------|
| 链路完整性 | **55/100** | Browser Hand 断裂、事件无人接收、run_id 丢失 |
| 参数一致性 | **70/100** | invoke 参数匹配但返回值不匹配、TOML 配置未生效 |
| 边界处理 | **50/100** | 无并发限制、无超时、审批不过期 |
| 状态管理 | **60/100** | 基本状态机存在但 browserHandStore 独立运作 |
| 安全资源 | **55/100** | Browser Hand 绕过审批是核心安全问题 |
**综合健康度: 58/100**
5 个 P1 级问题集中在此模块run_id 丢失、Browser 断裂、绕过审批、无并发限制、无超时)。修复 P1 后预计可提升至 75+。