# ZCLAW 深度二次审计补充报告 > **审计日期**: 2026-04-02 > **基线**: V11 全面审计报告(22 项发现) > **方法**: 5 维并行深度审计 > **新增发现**: 22 项(2 P0 + 9 P1 + 10 P2 + 3 P3 + 1 确认项) --- ## 1. 审计维度 | # | 维度 | 发现数 | |---|------|--------| | 1 | Rust crate 交叉依赖 + 编译状态 | 5 | | 2 | 前端 Store ↔ Tauri 命令参数签名全量比对 | 5 | | 3 | Sprint 1-4 修复代码质量验证 | 1 | | 4 | 安全/竞态/资源泄漏扫描 | 9 | | 5 | 测试覆盖缺口 + admin-v2 API 对齐 | 4 | --- ## 2. 新增 P0 发现(2 项) ### SEC2-P0-01: skill_execute 反序列化崩溃 | 维度 | 详情 | |------|------| | **问题** | 前端传递空 `context: {}`,但 Rust `SkillContext` 要求 `agent_id: String` 和 `session_id: String`(非 Option)。空 JSON 无法反序列化。 | | **前端** | `desktop/src/lib/kernel-skills.ts:110-114` | | **Rust** | `desktop/src-tauri/src/kernel_commands/skill.rs:290-296` | | **触发条件** | 用户在前端执行任何 Skill | | **影响** | 运行时 serde 反序列化失败,Skill 执行必然报错 | ### SEC2-P0-02: TaskTool::default() 潜在 panic | 维度 | 详情 | |------|------| | **问题** | `TaskTool` 实现了 `Default` trait,但 `default()` 调用 `unimplemented!()` | | **文件** | `crates/zclaw-runtime/src/tool/builtin/task.rs:59` | | **触发条件** | 任何泛型约束 `T: Default` 触发 `TaskTool::default()` 时 | | **影响** | 运行时 panic(虽然正常路径使用 `TaskTool::new()` 规避) | | **建议** | 移除 `impl Default for TaskTool`,或提供合理的默认值 | --- ## 3. 新增 P1 发现(9 项) ### SEC2-P1-01: FactStore trait 零实现 | 维度 | 详情 | |------|------| | **问题** | `FactStore` trait 在 `zclaw-memory/src/fact.rs:141` 定义,全 workspace 无任何 `impl FactStore for T`。`MemoryStore` 有同名方法但未实现 trait。`dyn FactStore` 模式完全不可用。 | | **文件** | `crates/zclaw-memory/src/fact.rs` | | **影响** | 架构层面功能缺口——trait 接口已定义但无法使用 | ### SEC2-P1-02: agent-templates API 路径缺少 /api/v1 前缀 | 维度 | 详情 | |------|------| | **问题** | 前端 saas-client.ts 中 agent-templates 路径缺少 `/api/v1` 前缀 | | **前端** | `desktop/src/lib/saas-client.ts:376,384`:`'/agent-templates/available'`、`'/agent-templates/${id}/full'` | | **后端** | 路由注册在 `/api/v1/agent-templates/...` | | **影响** | 请求发送到错误 URL,必然返回 404 | ### SEC2-P1-03: hand-execution-complete 事件无前端监听 | 维度 | 详情 | |------|------| | **问题** | Rust 在 `hand.rs:279` 和 `approval.rs:130` emit `hand-execution-complete`,但前端无任何 `listen()` 调用 | | **影响** | Hand 异步执行完成后,前端无法收到通知,用户不知道执行结果 | ### SEC2-P1-04: InMemoryStorage RwLock unwrap() 级联 panic 风险 | 维度 | 详情 | |------|------| | **问题** | `InMemoryStorage` 中 6 处 `std::sync::RwLock` 的 `.unwrap()`。若锁被 poisoned(某线程 panic),后续所有调用级联崩溃 | | **文件** | `crates/zclaw-growth/src/viking_adapter.rs:137,143,148,190,202,208,214` | | **风险** | 低概率但后果严重 | ### SEC2-P1-05: HandRun 持久化错误静默忽略(3 处) | 维度 | 详情 | |------|------| | **问题** | `let _ = memory.save_hand_run(&run).await` 等 3 处 HandRun 持久化错误被忽略 | | **文件** | `crates/zclaw-kernel/src/kernel/approvals.rs:88,91,120` | | **影响** | DB 不可用时 run 状态丢失,UI 无法显示执行结果 | | **建议** | 至少 log warning | ### SEC2-P1-06: FTS 索引更新失败静默忽略(3 处) | 维度 | 详情 | |------|------| | **问题** | 全文搜索索引 DELETE/INSERT 操作失败被 `let _ =` 忽略 | | **文件** | `crates/zclaw-growth/src/storage/sqlite.rs:384,390,605` | | **影响** | 搜索结果不一致(stale index) | ### SEC2-P1-07: Worker dispatch 失败静默忽略(4 处) | 维度 | 详情 | |------|------| | **问题** | `let _ = state.worker_dispatcher.dispatch(...)` 4 处忽略 | | **文件** | `crates/zclaw-saas/src/knowledge/handlers.rs:220,262,331,539` | | **影响** | embedding 生成等后台任务可能静默丢失 | ### SEC2-P1-08: Desktop 前端零测试 | 维度 | 详情 | |------|------| | **问题** | `desktop/src/` 中零个 `.test.ts`/`.test.tsx` 文件,包括 chatStore、agentStore 等核心 store | | **影响** | 前端任何回归只能通过手工测试发现 | ### SEC2-P1-09: record_key_usage 错误忽略导致计费数据丢失风险 | 维度 | 详情 | |------|------| | **问题** | `let _ = record_key_usage(...)` 忽略写入错误 | | **文件** | `crates/zclaw-saas/src/relay/service.rs:376` | | **影响** | 可能导致计费数据丢失 | --- ## 4. 新增 P2 发现(10 项) | ID | 问题 | 文件 | |----|------|------| | SEC2-P2-01 | `hmac`/`sha1` 在 zclaw-hands Cargo.toml 中声明但代码零引用 | `crates/zclaw-hands/Cargo.toml` | | SEC2-P2-02 | `serde_yaml` 版本不一致:desktop 用 0.9,pipeline 用 2(不同 package) | 各 `Cargo.toml` | | SEC2-P2-03 | `sqlx-postgres v0.7.4` 使用了未来 Rust 版本将拒绝的语法 | `Cargo.lock` | | SEC2-P2-04 | `generate_embedding.rs:107` embedding 生成被注释掉(`// TODO`) | `crates/zclaw-saas/src/workers/generate_embedding.rs` | | SEC2-P2-05 | ~10 处 `tokio::spawn` 返回 JoinHandle 未绑定,无法优雅停止 | kernel + saas 多处 | | SEC2-P2-06 | Telemetry 审计日志批量 INSERT 绑定可能与 SQL 模板不匹配 | `crates/zclaw-saas/src/telemetry/service.rs:205-213` | | SEC2-P2-07 | Scheduler 串行执行 trigger,长 hand 阻塞后续调度 | `crates/zclaw-kernel/src/scheduler.rs:117-153` | | SEC2-P2-08 | `format!("FROM {}", table)` 模式虽当前安全(硬编码),但违反防御原则 | `crates/zclaw-saas/src/db.rs:874,880` | | SEC2-P2-09 | `hand_run_status` 前端传递多余 `handName` 参数(Rust 只接收 `run_id`) | `desktop/src/lib/kernel-hands.ts:95` | | SEC2-P2-10 | `kernel_apply_saas_config` TOML 写入不支持多行值 | `desktop/src-tauri/src/kernel_commands/lifecycle.rs` | --- ## 5. 新增 P3 发现(3 项) | ID | 问题 | 文件 | |----|------|------| | SEC2-P3-01 | A2A Router 4 个 RwLock 获取顺序未文档化 | `crates/zclaw-protocols/src/a2a.rs:239-245` | | SEC2-P3-02 | Admin-v2 Role 类型:后端 `is_system` 字段前端未映射,前端 `account_count` 后端无字段 | `admin-v2/src/types/index.ts` vs `role/types.rs` | | SEC2-P3-03 | Admin-v2 Billing/Knowledge/Roles 三个页面缺测试(Billing、Knowledge 页面 + Roles 新页面) | `admin-v2/tests/` | --- ## 6. 确认项(审计验证结果) ### 6.1 编译状态 - `cargo check --workspace` **编译成功**(33.60s) - 2 个 warning:`RegisterDeviceRequest` 可见性(P2)、`sqlx-postgres` 未来兼容性(P1) ### 6.2 Sprint 1-4 修复验证 | 修复 | 验证结果 | |------|---------| | trigger_update 参数扁平化 | **通过** — 前后端参数完全匹配 | | SaaS config sync 传播 | **通过** — kernel_apply_saas_config 正确桥接 | | Deprecated 代码清理 | **通过** — 无残留引用 | | Admin Roles 页面 | **通过** — API 路径与 SaaS 路由匹配 | | ToolDefinition 去重 | **通过** — 使用 pub use | | Knowledge handler 修复 | **通过** — 连接到 service 层 | | Task 结果持久化 | **通过** — migration + 读写一致 | ### 6.3 Admin-v2 ↔ SaaS API 对齐 **结论:100% 对齐。** 所有 admin-v2 前端调用的 API 端点在后端都有对应路由注册,包括: - 9 个 knowledge 路径(categories/items/analytics/search/recommend/import/versions/rollback/batch) - 5 个 role 路径 + 5 个 permission-template 路径 - 6 个 billing 路径 - 2 个 telemetry 路径 ### 6.4 Feature Gate 一致性 传播链完全正确: ``` desktop --multi-agent--> zclaw-kernel --multi-agent--> zclaw-protocols --a2a--> [a2a module] zclaw-skills --wasm--> [wasmtime, wasmtime-wasi] ``` ### 6.5 认证覆盖 SaaS 后端三层路由分离无遗漏:公共路由 + 受保护路由 + Relay 独立中间件链。 --- ## 7. 测试覆盖统计 ### 7.1 Rust 测试分布 | Crate | 测试数 | 关键缺口 | |-------|--------|----------| | zclaw-hands | 155 | - | | zclaw-saas | 92 | 集中在工具函数,handler/scheduler 0 测试 | | zclaw-growth | 75 | - | | zclaw-pipeline | 59 | - | | zclaw-types | 57 | - | | zclaw-kernel | 52 | capabilities/registry/trigger_manager 0 测试 | | zclaw-runtime | 42 | - | | zclaw-memory | 25 | - | | zclaw-skills | 22 | - | | zclaw-protocols | 5 | 极低 | | **总计** | **584** | | ### 7.2 零测试关键模块 | 模块 | 公开函数数 | 严重度 | |------|-----------|--------| | kernel_commands/ (41 Tauri 命令) | 41 | P0 | | browser/commands.rs (23 命令) | 23 | P0 | | SaaS scheduler.rs | 3 公开函数 | P1 | | SaaS knowledge/handlers.rs (561 行) | ~15 | P1 | | SaaS relay/service.rs | ~20 | P1 | | SaaS billing/ | ~10 | P1 | | Desktop frontend | 全部 | P1 | ### 7.3 Admin-v2 测试 - **测试框架**: Vitest + jsdom + MSW - **测试用例**: 322 个 - **覆盖页面**: 10/13(缺失 Billing、Knowledge、Roles) - **覆盖 service**: 1/17(仅 request.ts) --- ## 8. 积极发现 1. **SQL 参数化规范**:SaaS 层绝大多数 SQL 使用 `sqlx::query(...).bind(...)` 参数化 2. **密钥保护**:JWT secret 使用 `secrecy::SecretString`,API key 日志仅输出 ID 3. **DashMap 死锁已规避**:所有 DashMap RefMut 在 `.await` 前释放 4. **SSE 背压设计**:有界 channel + 信号量限制 + CancellationToken 5. **密码版本控制**:password_version 确保 JWT 失效 6. **MCP Transport**:实现 Drop trait 清理子进程 7. **DB 连接池监控**:30s 周期日志 + 80% 告警 8. **Admin-v2 API 对齐**:所有前端调用都有后端路由对应 --- ## 9. 综合风险矩阵(V11 + 二次审计合并) | 严重度 | V11 原始 | 二次审计新增 | 合计 | |--------|----------|-------------|------| | P0 | 1 (误报) | **2** | 2 | | P1 | 3 | **9** | 12 | | P2 | 6 | **10** | 16 | | P3 | 8 | **3** | 11 | | P4 | 5 | 0 | 5 | | **总计** | **23** | **24** | **46** | --- ## 10. 支付安全深度审计 V3(2026-04-02) > **审计范围**: `crates/zclaw-saas/src/billing/` 全部文件(payment.rs, service.rs, handlers.rs, types.rs, mod.rs) > **审计方法**: 3 维并行(Security Engineer + Code Reviewer + Senior Developer) > **基线**: V2 审计后的 25 项修复全部落地 ### 10.1 审计结论 | 指标 | 结果 | |------|------| | **CRITICAL** | **0** | | **HIGH** | **0** | | **MEDIUM** | **1**(已修复) | | **LOW** | **3**(pre-existing / cosmetic) | ### 10.2 已修复项(本次修复) | ID | 严重度 | 问题 | 修复 | |----|--------|------|------| | PAY-FIX-01 | MEDIUM | `truncate_str` 使用 `s.len()`(字节数)判断是否截断,但 `chars().take()` 按字符截断。中文等多字节字符串在 200 字节内但超过 200 字符时截断错误 | 改为先 `chars().collect::>()` 再按 `.len()` 判断字符数 | ### 10.3 已验证通过的关键安全措施(25 项) #### 事务与竞态保护(6 项) - [x] `create_payment` 在事务中创建 invoice + payment(原子性) - [x] `handle_payment_callback` 使用 `SELECT ... FOR UPDATE` 锁定行(防 TOCTOU) - [x] `get_or_create_usage` 使用 `INSERT ON CONFLICT DO NOTHING`(防重复创建) - [x] 金额交叉验证:生产环境 `callback_amount` 为 None 时拒绝 - [x] 幂等保护:已处理的 payment(status != pending)直接返回 - [x] 事务失败时正确 rollback #### 支付回调安全(6 项) - [x] Alipay 验签:生产环境强制 RSA2 验签,缺公钥拒绝 - [x] WeChat 解密:AES-256-GCM + nonce 长度校验(12 字节) - [x] trade_no 缺失时返回错误而非静默忽略 - [x] 日志安全:`sanitize_log` 只保留字母数字和 `-` `_` - [x] 通用错误对外返回(不泄露内部细节) - [x] 回调路由正确分离到 public_routes(无需认证) #### 密钥保护(4 项) - [x] `PaymentConfig` 自定义 Debug impl,敏感字段显示 `***REDACTED***` - [x] `alipay_private_key`、`alipay_public_key`、`wechat_api_v3_key` 标记 `skip_serializing` - [x] JWT 密钥使用 `secrecy::SecretString` - [x] API key 日志仅输出 ID 不输出值 #### 货币精度(2 项) - [x] 分→元转换使用整数运算(`amount_cents / 100` + `amount_cents % 100`) - [x] WeChat 金额保持整数分(无浮点转换) #### HTML 安全(2 项) - [x] Mock 支付页面使用 `html_escape()` 转义用户输入 - [x] Mock 确认页面的 `msg` 变量由服务端控制(安全) #### SQL 安全(3 项) - [x] 所有 SQL 使用参数化查询(`sqlx::query(...).bind(...)`) - [x] `increment_dimension` 使用白名单分支(非动态列名) - [x] `failure_reason` 截断到 200 字符 #### 整体架构(2 项) - [x] 路由分离:`routes()` 需认证,`callback_routes()` 公开 - [x] 批量递增 `increment_dimension_by` 替代循环查询 ### 10.4 剩余 LOW 项(不影响安全,可后续优化) | ID | 说明 | 建议 | |----|------|------| | PAY-LOW-01 | `reqwest::Client::new()` 每次 WeChat 支付创建新实例 | 改为 AppState 中共享 Client | | PAY-LOW-02 | `BootstrapScreen` 缺少 dark mode 变体 | 添加 `dark:bg-gray-900` | | PAY-LOW-03 | `aside.w-64.sidebar-open` class 定义但无 JS 切换逻辑 | 添加汉堡菜单或移除 | ### 10.5 审计签名 - **Security Engineer**: 0 CRITICAL, 0 HIGH — 支付链路安全措施完备 - **Code Reviewer**: 25 项修复全部正确实现,无回归 - **Senior Developer**: 1 MEDIUM 已修复(truncate_str),代码质量良好