Files
zclaw_openfang/docs/features/SECONDARY_AUDIT_V11.md
iven 8898bb399e
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 reports + feature docs + skills + admin-v2 + config sync
Update audit tracker, roadmap, architecture docs,
add admin-v2 Roles page + Billing tests,
sync CLAUDE.md, Cargo.toml, docker-compose.yml,
add deep-research / frontend-design / chart-visualization skills

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 19:25:00 +08:00

330 lines
14 KiB
Markdown
Raw 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.

# 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.9pipeline 用 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. 支付安全深度审计 V32026-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::<Vec<char>>()` 再按 `.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] 幂等保护:已处理的 paymentstatus != 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代码质量良好