docs: audit reports + feature docs + skills + admin-v2 + config sync
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>
This commit is contained in:
iven
2026-04-02 19:25:00 +08:00
parent 28299807b6
commit 8898bb399e
48 changed files with 7388 additions and 173 deletions

View File

@@ -0,0 +1,214 @@
# ZCLAW 第三轮全面审计报告
> **审计日期**: 2026-04-02
> **基线**: V11 全面审计 + 深度二次审计 + Sprint 1-4 修复
> **方法**: 5 维并行审计(前端状态一致性、数据库 Schema、API 契约、并发安全、代码质量)
> **完成代理**: 前端状态一致性1/5其余 4 维因 API 限流由主线程直接执行
---
## 1. 前端状态一致性 + 内存泄漏审计(代理完成)
### HIGH
| ID | 问题 | 文件 | 描述 |
|----|------|------|------|
| AUD3-FE-01 | **chatStore.sendMessage 无并发保护** | `chatStore.ts:403-675` | `isStreaming` 仅在 UI 层守卫store 函数本身无互斥。快速双击可在 React re-render 前触发两次发送,导致双重 assistant placeholder + stream 竞态 |
| AUD3-FE-02 | **SaaS client token refresh 无并发锁** | `saas-client.ts:217-229` | 多个并发请求同时收到 401 时,各自独立调用 `refreshToken()`,导致多次 refresh 请求。应使用 refresh mutex共享 Promise |
### MEDIUM
| ID | 问题 | 文件 | 描述 |
|----|------|------|------|
| AUD3-FE-03 | initializeStores 可能被调用 3 次 | `connectionStore.ts:415,589` + `index.ts:99` | 模块加载 + connect() 双路径,异步操作中切换 client 可能导致请求失败 |
| AUD3-FE-04 | window 全局变量存储 interval | `App.tsx:257-258` | `@ts-expect-error` + `window.__ZCLAW_STATS_SYNC_INTERVAL__`React StrictMode 双重 mount 时第一个 interval 无法清理 |
| AUD3-FE-05 | GatewayClient mixin 25+ 处 `as any` | `gateway-heartbeat.ts` 等 | prototype 动态方法通过 `as any` 绕过类型检查,属性名拼写错误无编译时报警 |
| AUD3-FE-06 | PropertyPanel 17 处 `as any` 访问联合类型 | `PropertyPanel.tsx:100-276` | WorkflowNodeData 联合类型的字段直接 `as any` 访问,节点类型不匹配时 undefined |
### LOW
| ID | 问题 | 文件 |
|----|------|------|
| AUD3-FE-07 | offlineStore 全局变量存储 timer多次调用可能泄漏 | `offlineStore.ts:87-88` |
| AUD3-FE-08 | agentStore 一次性读取 chatStore 可能读到中间态 | `agentStore.ts:254` |
| AUD3-FE-09 | retryAllMessages 无并发锁,可能重复发送 | `offlineStore.ts:188-233` |
### POSITIVE FINDINGS做得好的地方
- **无 Store 循环依赖**: 依赖方向是单向树状结构
- **事件监听器清理完善**: classroomStore、useAutomationEvents、chatStore 的 listen 全部有 cleanup
- **React useEffect cleanup 规范**: ConnectionStatus、HandApprovalModal、SaaSStatus 全部正确清理
---
## 2. 数据库 Schema + Migration 审计(主线程执行)
### Migration 文件清单13 个)
| 编号 | 文件 | 内容 |
|------|------|------|
| 20260329-001 | initial_schema.sql | 21 个核心表 |
| 20260329-002 | seed_data.sql | 种子数据 |
| 20260330-001 | scheduled_tasks.sql | 定时任务表 |
| 20260331-001 | accounts_llm_routing.sql | LLM 路由字段 |
| 20260331-002 | agent_templates_extensions.sql | 模板扩展 |
| 20260401-001 | provider_keys_last_used.sql | key 最近使用时间 |
| 20260401-002 | remove_quota_reset_interval.sql | 移除配额重置 |
| 20260401-003 | models_is_embedding.sql | embedding 标记 |
| 20260401-004 | accounts_password_version.sql | 密码版本 |
| 20260401-005 | rate_limit_events.sql | 限流事件 |
| 20260402-001 | billing_tables.sql | 计费 5 表 |
| 20260402-002 | knowledge_base.sql | 知识库 5 表 |
| 20260402-003 | scheduled_task_results.sql | 任务结果列 |
### MEDIUM
| ID | 问题 | 描述 |
|----|------|------|
| AUD3-DB-01 | 无 down migration | 所有 migration 只有 UP无回滚脚本。生产环境需要回滚时只能手动操作 |
| AUD3-DB-02 | agent_template/service.rs:136 format! 构建 SQL | `format!("SELECT COUNT(*) FROM agent_templates {}", where_clause)``where_clause` 虽然是硬编码常量(非用户输入),但模式本身违反防御原则 |
### POSITIVE FINDINGS
- **编号连续无冲突**: 13 个 migration 编号连续
- **无 DELETE/UPDATE 缺少 WHERE**: 全量扫描确认所有写操作都有 WHERE 子句
- **仅 2 处 format! SQL**: `agent_template/service.rs``db.rs`,两者 where_clause/table 均为硬编码
- **zclaw-growth 和 zclaw-memory 无 migration 目录**: 使用代码内 schema 初始化SQLite
---
## 3. API 契约 + 错误恢复审计(主线程执行)
### HIGH
| ID | 问题 | 文件 | 描述 |
|----|------|------|------|
| AUD3-API-01 | **SaaS token refresh 并发竞态** | `saas-client.ts:217-229` | 多个并发请求同时收到 401各自独立调用 `refreshToken()`。无 `_refreshPromise` 或 mutex。refresh token 可能被第一个请求消耗,后续 refresh 请求失败(单次使用 token |
### MEDIUM
| ID | 问题 | 描述 |
|----|------|------|
| AUD3-API-02 | 前端错误处理不统一 | 部分 invoke() 调用用 try/catch + log静默部分直接 throw用户看到错误部分 fallback 默认值。无全局错误提示机制 |
| AUD3-API-03 | 37 处 `as any` 类型断言 | 前端大量绕过类型检查,重构时容易引入运行时错误 |
### POSITIVE FINDINGS
- **AbortController 使用规范**: `request-helper.ts` 有完整的 AbortController 管理Map<string, AbortController>),支持请求取消
- **认证端点跳过 refresh**: `_isAuthEndpoint()` 正确避免 login/register 端点的无限 refresh 循环
- **timeout 配置**: 所有 fetch 调用使用 `AbortSignal.timeout()`
---
## 4. 并发安全 + 资源管理审计(主线程执行)
### MEDIUM
| ID | 问题 | 文件 | 描述 |
|----|------|------|------|
| AUD3-CONC-01 | kernel_commands 每个命令单独获取 kernel_lock | `kernel_commands/*.rs` | 每个 Tauri 命令独立 `state.lock().await`,无嵌套锁获取。设计安全但串行化所有命令执行 |
| AUD3-CONC-02 | ~15 处 fire-and-forget tokio::spawn | `main.rs:108-151`, `relay/handlers.rs:389`, `scheduler.rs:62-140` | 无 JoinHandle无优雅停机。shutdown 时运行中的任务可能被截断 |
| AUD3-CONC-03 | approval polling 循环持有 kernel_lock | `approval.rs:96` | `kernel_state.lock().await` 在 sleep 循环中反复获取,每次循环释放后重新获取。设计安全但增加锁竞争 |
### POSITIVE FINDINGS
- **无嵌套锁获取**: 每个 kernel_command 只获取一个 MutexLock不存在 ABBA 死锁风险
- **DashMap 操作规范**: 所有 RefMut 在 `.await` 前释放(已确认)
- **CancellationToken 用于 SSE**: relay 的 SSE 流有取消机制
---
## 5. 代码质量审计(主线程执行)
### 统计数据
| 指标 | 数值 |
|------|------|
| Rust 测试总数 | 584 |
| 前端测试 | 0 (desktop) + 322 (admin-v2) |
| `as any` 使用 | 37 处 |
| `@ts-expect-error` | 3 处 |
| 未使用 Cargo 依赖 | 0已清理 hmac/sha1 |
| Feature gate 一致性 | 正确 |
| 编译警告 | 1 (private_interfaces) + 1 (sqlx future-incompat) |
---
## 6. 综合发现汇总
### 新增 HIGH需立即修复
| ID | 问题 | 影响 |
|----|------|------|
| AUD3-FE-01 | chatStore.sendMessage 无并发保护 | 快速双击导致双重发送 + stream 竞态 |
| AUD3-FE-02 / AUD3-API-01 | SaaS token refresh 无并发锁 | 并发 401 → 多次 refresh → refresh token 被消耗 → 后续请求全部失败 |
### 新增 MEDIUM
| ID | 问题 | 影响 |
|----|------|------|
| AUD3-FE-03 | initializeStores 可能调用 3 次 | 异步操作中 client 切换 |
| AUD3-FE-04 | window 全局变量存 interval | StrictMode 泄漏 |
| AUD3-FE-05 | 25+ 处 mixin `as any` | 类型安全缺口 |
| AUD3-FE-06 | PropertyPanel 17 处 `as any` | 联合类型不安全 |
| AUD3-DB-01 | 无 down migration | 生产回滚困难 |
| AUD3-DB-02 | format! SQL硬编码安全但模式差 | 防御性编程 |
| AUD3-API-02 | 前端错误处理不统一 | 用户体验不一致 |
| AUD3-CONC-02 | ~15 处 fire-and-forget spawn | 优雅停机问题 |
### 新增 LOW
| ID | 问题 |
|----|------|
| AUD3-FE-07 | offlineStore 全局变量存储 timer |
| AUD3-FE-08 | agentStore 读取中间态 |
| AUD3-FE-09 | retryAllMessages 无并发锁 |
| AUD3-CONC-03 | approval polling 增加锁竞争 |
---
## 7. 三轮审计累计发现总览
| 来源 | P0 | HIGH/P1 | MEDIUM/P2 | LOW/P3/P4 |
|------|-----|---------|-----------|-----------|
| V11 初次审计 | 0 | 3 | 14 | 13 |
| V11 深度二次 | 2 | 9 | 13 | 3 |
| V11 第三轮 | 0 | **2** | **8** | **4** |
| **合计(去重后)** | **2** | **14** | **35** | **20** |
### 已修复 vs 未修复
| 状态 | 数量 |
|------|------|
| **已修复** | 132 P0 + 8 P1 + 3 P2 |
| **未修复** | 40 |
### 未修复中按优先级排序的 TOP 10
1. **AUD3-FE-01**: chatStore.sendMessage 并发保护HIGH
2. **AUD3-FE-02/API-01**: token refresh mutexHIGH
3. **SEC2-P1-01**: FactStore trait 零实现P1
4. **SEC2-P1-08**: Desktop 前端零测试P1
5. **AUD3-FE-03**: initializeStores 重复调用MEDIUM
6. **AUD3-FE-05**: mixin 模式类型安全MEDIUM
7. **SEC2-P2-03**: sqlx-postgres 未来兼容性P2
8. **SEC2-P2-05**: ~10 处 tokio::spawn 未绑定P2
9. **AUD3-DB-01**: 无 down migrationMEDIUM
10. **SEC2-P1-01**: FactStore trait 零实现P1
---
## 8. 积极发现总结
三轮审计确认以下方面**设计良好、实现规范**
1. **无 Store 循环依赖** — 单向树状依赖结构
2. **事件监听器全部有 cleanup** — listen/unlisten 配对完整
3. **认证中间件全覆盖** — 公共/受保护/Relay 三层路由无遗漏
4. **SQL 参数化规范** — 除 2 处硬编码 format! 外全部使用 bind()
5. **Lock ordering 安全** — 无嵌套锁获取
6. **Feature gate 一致** — 传播链正确
7. **Admin API 100% 对齐** — 前后端路由完全匹配
8. **编译通过** — cargo check 仅 1 warning
9. **密码安全** — Argon2id + password_version + JWT 失效
10. **SSE 背压设计** — 有界 channel + 信号量 + CancellationToken