# 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),支持请求取消 - **认证端点跳过 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 未修复 | 状态 | 数量 | |------|------| | **已修复** | 13(2 P0 + 8 P1 + 3 P2) | | **未修复** | 40 | ### 未修复中按优先级排序的 TOP 10 1. **AUD3-FE-01**: chatStore.sendMessage 并发保护(HIGH) 2. **AUD3-FE-02/API-01**: token refresh mutex(HIGH) 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 migration(MEDIUM) 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