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
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>
14 KiB
14 KiB
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. 积极发现
- SQL 参数化规范:SaaS 层绝大多数 SQL 使用
sqlx::query(...).bind(...)参数化 - 密钥保护:JWT secret 使用
secrecy::SecretString,API key 日志仅输出 ID - DashMap 死锁已规避:所有 DashMap RefMut 在
.await前释放 - SSE 背压设计:有界 channel + 信号量限制 + CancellationToken
- 密码版本控制:password_version 确保 JWT 失效
- MCP Transport:实现 Drop trait 清理子进程
- DB 连接池监控:30s 周期日志 + 80% 告警
- 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::<Vec<char>>() 再按 .len() 判断字符数 |
10.3 已验证通过的关键安全措施(25 项)
事务与竞态保护(6 项)
create_payment在事务中创建 invoice + payment(原子性)handle_payment_callback使用SELECT ... FOR UPDATE锁定行(防 TOCTOU)get_or_create_usage使用INSERT ON CONFLICT DO NOTHING(防重复创建)- 金额交叉验证:生产环境
callback_amount为 None 时拒绝 - 幂等保护:已处理的 payment(status != pending)直接返回
- 事务失败时正确 rollback
支付回调安全(6 项)
- Alipay 验签:生产环境强制 RSA2 验签,缺公钥拒绝
- WeChat 解密:AES-256-GCM + nonce 长度校验(12 字节)
- trade_no 缺失时返回错误而非静默忽略
- 日志安全:
sanitize_log只保留字母数字和-_ - 通用错误对外返回(不泄露内部细节)
- 回调路由正确分离到 public_routes(无需认证)
密钥保护(4 项)
PaymentConfig自定义 Debug impl,敏感字段显示***REDACTED***alipay_private_key、alipay_public_key、wechat_api_v3_key标记skip_serializing- JWT 密钥使用
secrecy::SecretString - API key 日志仅输出 ID 不输出值
货币精度(2 项)
- 分→元转换使用整数运算(
amount_cents / 100+amount_cents % 100) - WeChat 金额保持整数分(无浮点转换)
HTML 安全(2 项)
- Mock 支付页面使用
html_escape()转义用户输入 - Mock 确认页面的
msg变量由服务端控制(安全)
SQL 安全(3 项)
- 所有 SQL 使用参数化查询(
sqlx::query(...).bind(...)) increment_dimension使用白名单分支(非动态列名)failure_reason截断到 200 字符
整体架构(2 项)
- 路由分离:
routes()需认证,callback_routes()公开 - 批量递增
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),代码质量良好