Files
zclaw_openfang/docs/features/AUDIT_ROUND3_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

215 lines
9.8 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 全面审计 + 深度二次审计 + 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