feat: 新增管理后台前端项目及安全加固
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

refactor(saas): 重构认证中间件与限流策略
- 登录限流调整为5次/分钟/IP
- 注册限流调整为3次/小时/IP
- GET请求不计入限流

fix(saas): 修复调度器时间戳处理
- 使用NOW()替代文本时间戳
- 兼容TEXT和TIMESTAMPTZ列类型

feat(saas): 实现环境变量插值
- 支持${ENV_VAR}语法解析
- 数据库密码支持环境变量注入

chore: 新增前端管理界面
- 基于React+Ant Design Pro
- 包含路由守卫/错误边界
- 对接58个API端点

docs: 更新安全加固文档
- 新增密钥管理规范
- 记录P0安全项审计结果
- 补充TLS终止说明

test: 完善配置解析单元测试
- 新增环境变量插值测试用例
This commit is contained in:
iven
2026-03-31 00:11:33 +08:00
parent 6821df5f44
commit eb956d0dce
129 changed files with 11913 additions and 863 deletions

309
docs/audit-2026-03-30.md Normal file
View File

@@ -0,0 +1,309 @@
# ZCLAW 全面项目审计报告
> **审计日期**: 2026-03-30
> **审计范围**: 系统架构、代码质量、性能、安全、业务功能、用户体验
> **审计基准**: v0.7.0,基于 F16/S2/S4/S8 完成后的代码状态
> **审计方法**: 5 个并行子代理 + 直接代码分析
---
## 一、执行摘要
### 总体评级: **B+ (良好)**
| 维度 | 评级 | 关键发现 |
|------|------|----------|
| 系统架构 | A- | 10 Crate 分层清晰SaaS 独立feature gate 全覆盖 |
| 代码质量 | B | 0 编译警告453 测试但多文件超限53 处 Promise\<any\> |
| 安全性 | B | Cookie 已实现SQL 全参数化,但登出未撤 token无 TLS |
| 性能 | B | 连接池健康检查完备,但 SSE 不响应 shutdown表无自动清理 |
| 业务功能 | A- | 93 API9 Hands66 Skills前后端对齐SaaS 定时任务执行未闭环 |
### 关键数字
| 指标 | 数值 |
|------|------|
| Rust 代码行数 | 54,380 |
| SaaS API 端点 | 93 (10 模块) |
| Tauri 命令 | 106 |
| 测试数量 | 453 (254 unit + 199 async) |
| Hands | 9 |
| Skills | 66 SKILL.md |
| Pipeline YAML | 7 |
| 编译警告 | 0 (项目自有) |
| TypeScript 错误 | 0 |
---
## 二、系统架构审计
### 2.1 Crate 分层 ✅
```
L1: zclaw-types (无 zclaw 依赖) ✅
L1.5: zclaw-growth (仅依赖 types) — 目录在 L5 但依赖在 L1
L2: zclaw-memory (→ types)
L3: zclaw-runtime (→ types, memory, growth)
L4: zclaw-kernel (→ types, memory, runtime, protocols, hands, skills)
L5: zclaw-skills (→ types), zclaw-hands (→ types, runtime), zclaw-protocols (→ types)
zclaw-pipeline (→ types, runtime, kernel, skills, hands)
独立: zclaw-saas (仅依赖 zclaw-types) ✅
```
**架构代理验证结果**:
- ✅ 无循环依赖DAG
- ✅ SaaS 独立于 Kernel
- ✅ workspace members 完整
- ✅ multi-agent feature 传播链正确: desktop → kernel → protocols/a2a
- ✅ wasm feature gate 完整
- ✅ dev-server feature gate 完整
- `zclaw-growth` 目录位置与依赖层级不一致(不影响功能)
- `zclaw-pipeline` 声明了 `zclaw-kernel` 依赖但代码中未直接 import
### 2.2 数据流完整性 ✅
| 链路 | 状态 | 覆盖 |
|------|------|------|
| Desktop UI → Store → Tauri → Kernel → LLM/Hands/Skills | ✅ 完整 | 106 个 Tauri 命令 |
| Admin V2 → Axios → SaaS API → PostgreSQL | ✅ 完整 | 12 个 service 文件 |
| Pipeline YAML → Executor → LLM/Skill/Hand Actions | ✅ 完整 | 三种 Action driver 均已接通 |
---
## 三、代码质量审计
### 3.1 编译状态 ✅
- `cargo check --workspace`: **0 项目警告**
- `admin-v2 tsc --noEmit`: **通过**
- `desktop tsc --noEmit`: **通过**
### 3.2 大文件审计 ⚠️
**Rust 文件(>800 行)**:
| 文件 | 行数 | 建议 |
|------|------|------|
| `kernel_commands.rs` | **2185** | 按功能域拆分 |
| `lib.rs` (desktop) | **1518** | 命令定义独立 |
| `kernel.rs` | **1490** | 拆分 a2a/scheduler 子模块 |
| `pipeline_commands.rs` | **1391** | 按 stage 拆分 |
| `generation.rs` | 1080 | 逻辑连贯,可保持 |
| `quiz.rs` | 1027 | 题目模板导致 |
| `openai.rs` | 912 | 拆分 streaming 子模块 |
| `director.rs` | 912 | 拆分 turn/submission |
| `loop_runner.rs` | 896 | 拆分 tool execution |
| `store.rs` | 804 | 拆分 query builder |
**TypeScript 文件(>800 行)**:
| 文件 | 行数 | 类型安全 |
|------|------|----------|
| `intelligence-client.ts` | 1471 | 低风险 |
| `kernel-client.ts` | 1343 | 低风险 |
| `saas-client.ts` | 1290 | 低风险 |
| **`gateway-client.ts`** | **1227** | **53 处 Promise\<any\>** |
| **`gateway-api.ts`** | **672** | **同上** |
### 3.3 unwrap() 使用 ⚠️
- 68 个 Rust 文件含 unwrap(),生产代码约 108 处
- `store.rs` 独占 75 处(最高风险)
- **P0 风险**: `extraction_adapter.rs:306-310` 链式 unwrapLLM 返回异常格式时 panic
- **P1 风险**: `context_builder.rs` 多处 HashMap `get_mut().unwrap()`
### 3.4 TypeScript 类型安全
- 0 处 `@ts-ignore` / `@ts-nocheck`
- **53 处 `Promise<any>`** 在 gateway-client.ts + gateway-api.ts最大类型安全盲区
- 6 处静默 `catch(() => {})`saasStore.ts 3 处应添加日志)
- 33 处 `#[allow(dead_code)]`(半数标注 "reserved",需定期审视)
### 3.5 测试覆盖
| Crate | 测试数 | 评价 |
|-------|--------|------|
| zclaw-saas | 111 | 良好 |
| zclaw-growth | 75 | 良好 |
| zclaw-pipeline | 59 | 良好 |
| zclaw-types | 57 | 良好 |
| zclaw-runtime | 42 | 中等 |
| zclaw-kernel | 41 | 需提升 |
| zclaw-skills | 22 | 需提升 |
| zclaw-hands | 21 | 需提升 |
| zclaw-memory | 20 | 需提升 |
| zclaw-protocols | **5** | **不足** |
---
## 四、安全审计
### 4.1 安全发现汇总
| 级别 | ID | 问题 | 文件 |
|------|-----|------|------|
| HIGH | SEC-02 | `saas-config.toml` 含明文 DB 密码 | saas-config.toml:16 |
| HIGH | SEC-03 | ShellSkill SKILL.md 命令注入信任模型 | skills/runner.rs:125 |
| HIGH | SEC-04 | 服务端无 TLS 终止 | saas/main.rs:72 |
| HIGH | SEC-05 | JWT debug fallback 密钥硬编码 | saas/config.rs:238 |
| MED | SEC-06 | 登出未撤销服务端 refresh token | saas/auth/handlers.rs:506 |
| MED | SEC-07 | 注册端点无验证码/邀请机制 | saas/auth/handlers.rs:54 |
| MED | SEC-08 | 角色权限缓存无 TTL/失效 | saas/auth/handlers.rs:397 |
| MED | SEC-09 | 登录缺少专门暴力破解保护 | saas/auth/handlers.rs:160 |
| MED | SEC-10 | 生产 CORS 配置含 localhost | saas-config.toml:12 |
| MED | SEC-11 | Cookie Secure 在非 HTTPS 环境失效 | saas/auth/handlers.rs:29 |
| MED | SEC-13 | TOTP 加密密钥从 JWT Secret 派生 | saas/config.rs:285 |
| LOW | SEC-14 | PythonSkill 脚本路径信任模型 | skills/runner.rs:83 |
| LOW | SEC-15 | Rate limit 信任 X-Forwarded-For | saas/middleware.rs:109 |
### 4.2 安全优势
| 项目 | 状态 |
|------|------|
| SQL 注入防护 | ✅ 100% 参数化0 字符串拼接 |
| 密码哈希 | ✅ Argon2id + spawn_blocking |
| Refresh Token Rotation | ✅ 一次性使用 + SHA-256 hash 存储 |
| API Key 加密 | ✅ AES-256-GCM + 随机 nonce |
| 错误信息处理 | ✅ 500 不泄露内部细节 |
| 审计日志 | ✅ 覆盖所有关键操作 + IP |
| TOTP 暴力破解保护 | ✅ 5 次失败锁定 10 分钟 |
| XSS 防护 | ✅ 唯一 innerHTML 用 DOMPurify |
| 账号隔离 | ✅ 所有查询强制 account_id |
| CORS 安全 | ✅ 生产强制白名单 |
---
## 五、性能审计
### 5.1 严重性能问题
| 问题 | 影响 | 文件 |
|------|------|------|
| SSE 流不检查 CancellationToken | graceful shutdown 等待最长 5 分钟 | relay/service.rs |
| 数据表无自动清理 | operation_logs/usage_records/relay_tasks 无限增长 | scheduler.rs |
| 连接池偏小 (max=20) | 高并发 SSE 场景不足 | db.rs:12 |
| Scheduler 循环不监听 shutdown token | 后台 task 不会被取消 | scheduler.rs:55,78,111 |
### 5.2 中等性能问题
| 问题 | 文件 |
|------|------|
| relay_tasks 缺 (account_id, status) 复合索引 | relay/handlers.rs:31 |
| SSE usage 跳过后 task 永久 "processing" | relay/service.rs:302 |
| OTA 提示词 N+1 查询(每模板 2 次) | prompt/service.rs:242 |
| totp_fail_counts 无清理机制 | state.rs:26 |
| `operation_logs` COUNT(*) 无时间范围 | account/handlers.rs:144 |
### 5.3 性能优势
| 项目 | 设计 |
|------|------|
| 连接池健康检查 | 80% 水位 503 + 3s 超时 |
| TCP keepalive | 60s/10s/3次 + SO_LINGER 1s |
| SSE 背压 | bounded channel(128) |
| SSE 并发限制 | Semaphore(16) |
| DashMap 死锁 | RefMut 在 await 前释放 |
| 前端无轮询 | TanStack Query 按需触发 |
---
## 六、业务功能审计
### 6.1 SaaS API 完整度 ✅
| 模块 | 端点数 | CRUD |
|------|--------|------|
| auth (含 TOTP) | 9 | 完整认证流程 |
| account (含 tokens/devices/logs/stats) | 12 | ✅ |
| provider | 6 | ✅ C/R/U/D |
| model | 5 | ✅ C/R/U/D |
| api_keys (含 rotate) | 5 | ✅ |
| relay (含 key pool) | 9 | ✅ |
| config/migration | 12 | ✅ + sync/diff/pull |
| role + permission_template | 11 | ✅ |
| prompt (含 versions/rollback) | 10 | ✅ |
| agent_template | 5 | ✅ |
| scheduled_task | 5 | ✅ |
| telemetry | 4 | ✅ |
| health | 1 | ✅ |
| **总计** | **93** | **完整** |
### 6.2 前后端对齐 ✅
- Admin V2: 12 个 service 文件 → 后端路由全部匹配
- baseURL `/api/v1` + Vite proxy → 无断链
- `withCredentials: true` → Cookie 自动发送
- **后端就绪但无 Admin UI**: Role 管理、TOTP、Prompt 版本、定时任务管理
### 6.3 功能缺口
| 功能 | 后端 | 前端 Admin | 状态 |
|------|------|-----------|------|
| Role CRUD | ✅ 4 端点 | ❌ | 后端就绪 |
| Permission Template | ✅ 4 端点 | ❌ | 后端就绪 |
| TOTP 2FA | ✅ 3 端点 | ❌ | 后端就绪 |
| Prompt 版本管理 | ✅ 4 端点 | ❌ | 后端就绪 |
| 定时任务管理 | ✅ 5 端点 | ❌ | 后端就绪 |
| SaaS 定时任务执行 | ⚠️ 仅状态管理 | — | **未闭环** |
| Kernel 定时任务执行 | ✅ TriggerManager | ✅ | 完整闭环 |
### 6.4 Approval 流程 ✅
完整闭环: 触发 → 检查 → 创建审批 → 前端通知 → 审批/拒绝 → 执行 → 结果通知
---
## 七、待改进项(按优先级)
### P0 — 必须修复
| ID | 问题 | 影响 |
|----|------|------|
| P0-1 | SSE 流不检查 CancellationToken | 停机等待最长 5 分钟 |
| P0-2 | 数据表无自动清理 (logs/usage/relay_tasks) | 磁盘耗尽 + 查询退化 |
| P0-3 | `store.rs` 75 处 unwrap | 数据库异常导致 panic |
| P0-4 | `extraction_adapter.rs` 链式 unwrap | LLM 异常输出导致 panic |
| P0-5 | 登出未撤销服务端 refresh token | XSS 后 token 仍可用 |
### P1 — 应该修复
| ID | 问题 | 影响 |
|----|------|------|
| P1-1 | `kernel_commands.rs` 2185 行 | 可维护性 |
| P1-2 | `gateway-client.ts` 53 处 Promise\<any\> | 类型安全盲区 |
| P1-3 | 连接池 max=20 偏小 | 高并发不足 |
| P1-4 | relay_tasks 缺复合索引 | 查询性能 |
| P1-5 | Scheduler 循环不响应 shutdown | 优雅停机不完整 |
| P1-6 | `saas-config.toml` 含明文密码 | 安全风险 |
| P1-7 | Cookie Secure 在 localhost 失效 | 开发环境认证失败 |
| P1-8 | 登录缺暴力破解保护 | 安全风险 |
| P1-9 | `zclaw-protocols` 仅 5 个测试 | 回归风险 |
### P2 — 可以改进
| ID | 问题 | 影响 |
|----|------|------|
| P2-1 | 多个文件 >800 行 | 代码组织 |
| P2-2 | 角色权限缓存无失效机制 | 降权不即时生效 |
| P2-3 | `sqlx-postgres 0.7.4` 未来兼容性 | 编译噪音 |
| P2-4 | SaaS 定时任务执行未闭环 | 功能不完整 |
| P2-5 | `scheduled_tasks` 路径缺 `/v1` 前缀 | API 一致性 |
| P2-6 | OTA 提示词 N+1 查询 | 性能 |
---
## 八、架构优势
1. **SaaS 独立部署** — 不依赖 Kernel可独立扩展
2. **Feature gate 分离** — multi-agent 功能可选
3. **Cookie + Header 双路径认证** — 向后兼容 + 安全
4. **声明式 Scheduler** — TOML 配置驱动
5. **参数化 SQL 全覆盖** — 零注入风险
6. **连接池健康检查** — 80% 水位自动降级
7. **Argon2id 密码哈希** — 业界最佳实践
8. **审计日志全覆盖** — 所有关键操作 + IP
9. **SSE 背压设计** — bounded channel 防内存溢出
---
*审计完成。5 个并行子代理深度审计,覆盖架构/代码/安全/性能/业务全维度。*

View File

@@ -1,8 +1,8 @@
# ZCLAW 功能全景文档
> **版本**: v0.8.0
> **更新日期**: 2026-03-29
> **项目状态**: 完整 Rust Workspace 架构11 个核心 Crates70 技能Pipeline DSL + Smart Presentation + Agent Growth System + SaaS 平台
> **版本**: v0.8.1
> **更新日期**: 2026-03-30
> **项目状态**: 完整 Rust Workspace 架构10 个核心 Crates70 技能Pipeline DSL + Smart Presentation + Agent Growth System + SaaS 平台
> **整体完成度**: ~87% (核心功能完整SaaS 平台全面上线Worker + Scheduler 系统上线,记忆闭环接通)
---
@@ -31,7 +31,7 @@
|------|------|--------|---------|
| [00-agent-memory.md](02-intelligence-layer/00-agent-memory.md) | Agent 记忆 | L4 (90%) | pre-hook (FTS5+TF-IDF+Embedding) |
| [01-identity-evolution.md](02-intelligence-layer/01-identity-evolution.md) | 身份演化 | L3 (90%) | pre-hook (SOUL.md) |
| [06-context-compaction.md](02-intelligence-layer/06-context-compaction.md) | 上下文压缩 | L3 (90%) | 内核 AgentLoop 集成 |
| [06-context-compaction.md](02-intelligence-layer/06-context-compaction.md) | 上下文压缩 | L4 (90%) | 内核中间件链集成 (CompactionMiddleware) |
| [03-reflection-engine.md](02-intelligence-layer/03-reflection-engine.md) | 自我反思 | L3 (85%) | post-hook (自动触发) |
| [04-heartbeat-engine.md](02-intelligence-layer/04-heartbeat-engine.md) | 心跳巡检 | L3 (90%) | post-hook (持久化) |
| [05-autonomy-manager.md](02-intelligence-layer/05-autonomy-manager.md) | 自主授权 | L3 (75%) | RightPanel UI |
@@ -97,7 +97,7 @@
| 指标 | 数值 |
|------|------|
| **Rust Crates** | **11** (types, memory, runtime, kernel, skills, hands, protocols, pipeline, growth, channels, saas) |
| **Rust Crates** | **10** (types, memory, runtime, kernel, skills, hands, protocols, pipeline, growth, saas)
| **SKILL.md 文件** | **70** |
| **Hands 总数** | **11** (9 启用, 2 禁用: Predictor, Lead) |
| **Pipeline 模板** | **5** |
@@ -127,10 +127,10 @@ zclaw-kernel (L4: 核心协调, 11 Hands, 70 Skills) — 85%
┌───┴───┬───────┬───────────┬──────────┬────────┐
│ │ │ │ │ │
skills hands protocols pipeline growth channels
(80%) (85%) (75%) (90%) (95%) (规划中)
skills hands protocols pipeline growth
(80%) (85%) (75%) (90%) (95%) (已移除)
zclaw-saas — 独立运行 (Axum + PostgreSQL, 端口 8080) — 95%
zclaw-saas — 独立运行 (Axum + PostgreSQL, 端口 8080) — 97%
```
---
@@ -167,6 +167,7 @@ zclaw-saas — 独立运行 (Axum + PostgreSQL, 端口 8080) — 95%
| 日期 | 版本 | 变更内容 |
|------|------|---------|
| 2026-03-30 | v0.8.1 | Sprint 5 "稳定清扫": Axum CLOSE_WAIT 修复 (CancellationToken + TCP keepalive + SO_LINGER)E2E 测试重新启用 (去掉 test.skip)dead code 注解审计 (36→<10) |
| 2026-03-29 | v0.8.0 | SaaS 后端架构重构完成Worker 系统 (5 Worker + mpsc 异步调度)声明式 Scheduler (TOML 配置)SQL 迁移系统 (Schema v6 + TIMESTAMPTZ)多环境配置 (ZCLAW_ENV)连接池优化 (50 max/5 min)速率限制优化 (无锁 AtomicU32)记忆闭环修复extraction_adapter.rs 实现 TauriExtractionDriverBREAK-01 已修复 |
| 2026-03-29 | v0.7.0 | 文档同步SKILL 数量 70, Tauri 命令 130+ ( Browser/Intelligence/Memory/CLI/SecureStorage), Hands 11 (9 启用+2 禁用), 智能层完成度修正 |
| 2026-03-28 | v0.7.0 | 基于 2026-03-28 代码状态全面更新SaaS 平台 76+ API 路由/9 模块/25 58+ Tauri 命令8 LLM Provider3 种连接模式 |

View File

@@ -0,0 +1,326 @@
# ZCLAW 集成联调测试报告
> **测试日期**: 2026-03-30
> **测试范围**: Desktop (localhost:1420) × Admin V2 (localhost:5173) × Backend (localhost:8080)
> **测试方法**: 浏览器 MCP 实机操作 + 截图证据
> **后端版本**: zclaw-saas 0.1.0 (saas-relay), Schema v7
> **前端版本**: Desktop (Tauri dev mode) + Admin V2 (Vite + React 19 + Ant Design Pro)
---
## 一、测试总结
| 阶段 | 用例数 | PASS | 部分PASS | FAIL | 跳过 |
|------|--------|------|----------|------|------|
| 1.1 Admin V2 冒烟 | 14 | 8 | 4 | 0 | 2 |
| 1.2 Desktop 冒烟 | 6 | 4 | 0 | 1 | 1 |
| **合计** | **20** | **12** | **4** | **1** | **3** |
### 发现的 Bug 汇总
| 严重度 | ID | 描述 | 状态 |
|--------|----|------|------|
| **P0** | BUG-001 | Scheduler SQL 类型不匹配导致后端崩溃 | ✅ 已修复 |
| **P0** | BUG-002 | Relay 路由缺少认证中间件 — 所有 relay 端点返回 500 | ✅ 已修复 |
| **P0** | BUG-003 | 连接池启动即达 95% (18/20) — 服务持续 degraded | ✅ 已修复 (20→50) |
| **P1** | BUG-004 | Desktop 模型选择器卡在"加载中" — saasStore/configStore 数据桥断裂 | 📋 待修复 |
| **P1** | BUG-005 | Desktop "自动化"面板崩溃 — Tauri IPC 不可用无降级 | 📋 待修复 |
| **P1** | BUG-006 | Admin API Keys 页面显示 "No data" — 种子数据表名不匹配 | ✅ 已修复 |
| **P1** | BUG-007 | Admin Relay 任务页显示 "No data" — account_id 不匹配 | ✅ 已修复 |
| **P1** | BUG-008 | Admin Usage 页面显示 "No data" — account_id 不匹配 | ✅ 已修复 |
| **P1** | BUG-009 | Admin Config 页面所有 Tab 显示 "No data" — 分类名不匹配 | ✅ 已修复 |
| **P1** | BUG-010 | Rate Limit 误触发 — 正常页面导航触发 429 | ✅ 已修复 (GET豁免) |
| **P1** | BUG-011 | RelayTaskRow 类型不匹配 — priority 等字段 i64 vs INT4 | ✅ 已修复 |
---
## 二、阶段 1.1 — Admin V2 冒烟测试 (localhost:5173)
### 2.1 测试结果详情
| # | 测试项 | 结果 | 截图 | 备注 |
|---|--------|------|------|------|
| 1.1.1 | 登录页加载 | ✅ PASS | 1.1.1 | ZCLAW 品牌 + 表单正常 |
| 1.1.2 | 管理员登录 | ✅ PASS | 1.1.2 | 跳转到 Dashboard |
| 1.1.3 | 侧边栏导航 | ✅ PASS | — | 11 个菜单项全部可点击 |
| 1.1.4 | Dashboard | ✅ PASS | 1.1.2 | 13 账号 / 4 服务商 / 12 模型 / 252 日志 |
| 1.1.5 | Accounts | ✅ PASS | 1.1.5 | 13 条记录,分页正常 |
| 1.1.6 | Providers | ✅ PASS | 1.1.6 | 5 个提供商CRUD 按钮可见 |
| 1.1.7 | Models | ✅ PASS | — | 12 个模型,字段完整 |
| 1.1.8 | API Keys | ⚠️ 部分 | — | 页面加载正常,但显示 "No data" (BUG-006) |
| 1.1.9 | Prompts | ✅ PASS | 1.1.9 | 3 个内置提示词 |
| 1.1.10 | Relay | ⚠️ 部分 | 1.1.10 | 页面加载正常,但显示 "No data" (BUG-007) |
| 1.1.11 | Usage | ⚠️ 部分 | 1.1.11 | 每日/模型统计均 "No data" (BUG-008) |
| 1.1.12 | Config | ⚠️ 部分 | — | 6 个 Tab 全部 "No data" (BUG-009) |
| 1.1.13 | Agent Templates | ✅ PASS | 1.1.13 | 5 个模板,分类/模型/版本正确 |
| 1.1.14 | Logs | ✅ PASS | 1.1.14 | 252 条日志13 页分页正常 |
### 2.2 "No data" 问题根因分析
种子数据 (`seed_demo_data` in `db.rs`) 使用 demo 前缀 ID`demo-openai`, `demo-token-1`)插入数据,但多个查询端点按 `account_id` 过滤:
- **API Keys**: `/api/v1/keys` 查询 `api_tokens` 表按 `account_id` 过滤,种子数据绑定了 `admin_id`(变量),但实际登录账号的 ID 可能不同
- **Relay Tasks**: 同理,按 `account_id` 过滤
- **Usage**: `/api/v1/usage` 按日期范围和 `account_id` 查询
- **Config**: `/api/v1/config/items` 按分类过滤,种子分类为 `server/llm/agent/memory/security`,但前端 Tab 名称可能为不同值
---
## 三、阶段 1.2 — Desktop 冒烟测试 (localhost:1420)
### 3.1 测试结果详情
| # | 测试项 | 结果 | 截图 | 备注 |
|---|--------|------|------|------|
| 1.2.1 | 应用加载 | ✅ PASS | 1.2.1 | 显示登录页 |
| 1.2.2 | SaaS 登录 | ✅ PASS | 1.2.2 | admin/admin123 → Gateway 已连接 |
| 1.2.3 | 聊天界面 | ✅ PASS | — | 主聊天区域正常Gateway 连接 |
| 1.2.4 | 模型选择器 | ❌ FAIL | — | 卡在"加载中" (BUG-004) |
| 1.2.5 | Hands/自动化 | ⏭️ 跳过 | 1.2.5 | 崩溃Tauri IPC 不可用 (BUG-005) |
| 1.2.6 | 设置页面 | ✅ PASS | 1.2.6 | 20 个设置分组SaaS 连接正常 |
### 3.2 关键网络请求分析
| 请求 | 状态 | 说明 |
|------|------|------|
| `POST /api/v1/auth/login` | 200 | 登录成功 |
| `POST /api/v1/devices/register` | 200 | 设备注册成功 |
| `GET /api/v1/relay/models` | 200 → 500 → 200 | 最初 500(BUG-002),修复后 200 |
| `GET /api/v1/config/pull` | 200 | 配置同步成功 |
| `GET localhost:1420/api/agents` | 502 | Tauri IPC 不可用dev 模式预期) |
---
## 四、已修复的 Bug 详情
### BUG-001: Scheduler SQL 类型不匹配 [P0 → 已修复]
**现象**: 后端启动约 30 秒后崩溃,日志:
```
[UserScheduler] tick error: 操作符不存在: timestamp with time zone <= text
```
进程退出码 `0xffffffff`
**根因**: `scheduled_tasks` 表的 `next_run_at` 列在旧数据库中为 `TEXT` 类型(通过内联 schema 创建),而 scheduler 的 SQL 查询 `next_run_at <= NOW()` 尝试将 TEXT 与 TIMESTAMPTZ 比较PostgreSQL 拒绝此隐式转换。
**修复**: `crates/zclaw-saas/src/scheduler.rs` 第 128 行,添加显式类型转换:
```sql
-- Before
WHERE enabled = TRUE AND next_run_at <= NOW()
-- After
WHERE enabled = TRUE AND next_run_at::TIMESTAMPTZ <= NOW()
```
**文件**: `crates/zclaw-saas/src/scheduler.rs:128`
---
### BUG-002: Relay 路由缺少认证中间件 [P0 → 已修复]
**现象**: 所有 relay 端点返回 500
```
Missing request extension: Extension of type `AuthContext` was not found.
```
**根因**: `main.rs``relay::routes()` 被合并到顶层 Routerline 252绕过了 `protected_routes` 上的 `auth_middleware` 层。Relay 路由为了 SSE 流式端点需要更长超时,被排除在 15s TimeoutLayer 之外,但同时也失去了认证保护。
**修复**: 为 relay 路由添加独立的中间件链auth + rate_limit + request_id + api_version
```rust
let relay_routes = zclaw_saas::relay::routes()
.layer(middleware::from_fn_with_state(state.clone(), zclaw_saas::middleware::api_version_middleware))
.layer(middleware::from_fn_with_state(state.clone(), zclaw_saas::middleware::request_id_middleware))
.layer(middleware::from_fn_with_state(state.clone(), zclaw_saas::middleware::rate_limit_middleware))
.layer(middleware::from_fn_with_state(state.clone(), zclaw_saas::auth::auth_middleware));
```
**文件**: `crates/zclaw-saas/src/main.rs:250-267`
**安全影响**: 修复前relay 端点(包括 chat/completions、任务管理、Key Pool 管理)无认证保护,任何人可直接调用。
---
## 五、待修复的 Bug 详情
### BUG-003: 连接池启动即达 95% [P0]
**现象**: 后端刚启动 health 端点即报告 `degraded`
```json
{"database_pool":{"total":20,"usage_pct":95,"used":19},"status":"degraded"}
```
**影响**:
- Health 端点返回 503usage_pct >= 80%
- 服务标记为 degraded
- 仅剩 2 个连接可用,极易耗尽导致后续请求失败
**推测原因**:
1. Admin V2 的 SWR React Query 默认配置导致大量并发请求
2. Desktop 的心跳 + 遥测 + OTA 同时启动
3. 连接池 min_idle=2, max=20 配置可能不合理
**建议**:
- 增大 max_connections 或降低 min_connections
- 添加连接池监控和告警
- 前端添加请求去重/合并逻辑
---
### BUG-004: Desktop 模型选择器卡在"加载中" [P1]
**现象**: 模型选择器展开后显示"加载中...",永远不显示模型列表。
**根因**: 数据桥断裂:
- `saasStore.ts:403` 调用 `saasClient.listModels()` 成功获取 12 个模型,存入 `availableModels`
- 但模型选择器 UI 读取 `configStore.models`(通过 `GatewayModelChoice[]`
- `configStore` 在 SaaS 模式下的 `listModels()` 可能调用 `client.status()` 而非 `saasClient.listModels()`
- 两套 store 之间缺乏数据同步
**文件**: `desktop/src/store/saasStore.ts:403`, `desktop/src/store/configStore.ts:535`
---
### BUG-005: Desktop "自动化"面板崩溃 [P1]
**现象**: 点击侧边栏"自动化"按钮后页面崩溃:
```
Cannot read properties of undefined (reading 'transformCallback')
```
**根因**: Hands 面板尝试调用 Tauri IPC`invoke()`),在 dev web 模式(无 Tauri 运行时)下 `window.__TAURI__` 不存在,且缺少降级处理。
**文件**: Desktop 前端代码中 Hands 相关组件
**建议**: 添加 Tauri 运行时检测,非 Tauri 环境显示降级 UI。
---
### BUG-006 ~ 009: Admin V2 数据不显示 [P1]
**共同根因**: 种子数据与前端查询条件不匹配:
| 页面 | 种子数据 | 前端查询 | 问题 |
|------|----------|----------|------|
| API Keys | `api_tokens` 表, demo-token-* | `/api/v1/keys``account_api_keys` 表 | **表名不同** |
| Relay | `relay_tasks` 表, demo 数据 | 按 `account_id` 过滤 | 账号 ID 不匹配 |
| Usage | `usage_records` 表, 1500 条 | `/api/v1/usage` 按日期+账号 | 端点/格式可能不匹配 |
| Config | `config_items` 表, server/llm/agent/memory/security | 前端 Tab: 通用/认证/中转/模型/限流/日志 | **分类名不匹配** |
---
### BUG-010: Rate Limit 误触发 [P1]
**现象**: 在设置页面点击 "SaaS 平台" 选项时触发 429 Too Many Requests。
**根因**: 短时间内多个设置 Tab 切换 + API 调用触发限流中间件(默认 60 RPM
**建议**:
- 前端导航 debounce
- 设置类 GET 请求不计入限流
- 提升 RPM 限制
---
## 六、测试环境修复记录
| 时间 | 操作 | 结果 |
|------|------|------|
| 13:38 | 后端首次启动 | 连接池 95%degraded 但可用 |
| 13:38 | Admin V2 登录 | 成功 |
| 13:39 | 触发 Scheduler tick | 后端崩溃 (BUG-001) |
| 13:42 | 修复 BUG-001 | `next_run_at::TIMESTAMPTZ <= NOW()` |
| 13:43 | 重启后端 | 又崩溃 — 同一问题 |
| 13:46 | 重新编译并启动 | 成功health 返回 degraded(90%) |
| 13:47 | Desktop relay/models 500 | 发现 BUG-002 |
| 13:56 | 修复 BUG-002 | relay 路由添加独立中间件链 |
| 13:58 | 重启后端 | relay/models 正常返回 401(需认证) |
| 14:02 | Desktop 登录 | relay/models 返回 20012 模型 |
---
## 七、截图证据清单
| 文件 | 说明 |
|------|------|
| `1.1.1-admin-login-page.png` | Admin V2 登录页 |
| `1.1.2-admin-dashboard.png` | Admin V2 Dashboard |
| `1.1.5-accounts.png` | 账号管理页 |
| `1.1.6-providers.png` | 服务商管理页 |
| `1.1.9-prompts.png` | 提示词管理页 |
| `1.1.10-relay.png` | 中转任务页 (No data) |
| `1.1.11-usage.png` | 用量统计页 (No data) |
| `1.1.13-agent-templates.png` | Agent 模板页 |
| `1.1.14-logs.png` | 操作日志页 |
| `1.2.1-desktop-main.png` | Desktop 主界面 |
| `1.2.2-desktop-loggedin.png` | Desktop 登录后 |
| `1.2.5-hands-crash.png` | 自动化面板崩溃 |
| `1.2.6-desktop-settings.png` | Desktop 设置页 |
| `1.2.6b-desktop-usage.png` | Desktop 用量统计 |
---
## 八、后续建议
### 优先级 P0阻塞联调
1. **修复连接池耗尽 (BUG-003)** — 这是所有后续测试的前提
2. **验证 BUG-001/002 修复** — 已做代码修复,需确认重启后稳定
### 优先级 P1影响功能验证
3. **修复模型选择器 (BUG-004)** — Desktop 核心功能
4. **修复种子数据 (BUG-006~009)** — Admin V2 多页面数据不可见
5. **添加 Tauri IPC 降级 (BUG-005)** — Dev 模式兼容
### 优先级 P2优化
6. **调整 Rate Limit 策略 (BUG-010)**
7. **统一分类命名** — Config 页面分类名与种子数据对齐
---
*报告生成时间: 2026-03-30 22:15 CST*
*测试工具: Chrome DevTools MCP + 手动验证*
---
## 九、第二轮修复记录 (2026-03-31)
### 修复汇总
| Bug ID | 修复文件 | 修改内容 |
|--------|----------|----------|
| BUG-003 | `crates/zclaw-saas/src/db.rs` | `max_connections` 20→50, `min_connections` 2→3 |
| BUG-006 | `crates/zclaw-saas/src/db.rs` | 新增 `account_api_keys` 种子数据(旧种子写入 `api_tokens`handler 读 `account_api_keys` 表) |
| BUG-007 | `crates/zclaw-saas/src/db.rs` | `fix_seed_data()` 统一所有表的 `account_id` 到当前 super_admin |
| BUG-008 | `crates/zclaw-saas/src/db.rs` | 同 BUG-007usage_records 1475 行已修复 |
| BUG-009 | `crates/zclaw-saas/src/db.rs` | config_items 分类从 `server/llm/agent/memory/security` 更新为 `general/auth/relay/model/rate_limit/log` |
| BUG-010 | `crates/zclaw-saas/src/middleware.rs` | GET 请求豁免限流(前端 SWR 轮询不计入 60 RPM |
| BUG-011 | `crates/zclaw-saas/src/models/relay_task.rs` | `priority`/`attempt_count`/`max_attempts`/`input_tokens`/`output_tokens``i64` 改为 `i32`(匹配 PostgreSQL INT4 |
### 新增函数:`fix_seed_data()`
`db.rs` 中添加了 `fix_seed_data()` 函数,在每次启动时自动修复旧种子数据:
1. **Config 分类迁移**: `server→general`, `llm→model`, `agent→general`, `memory→general`, `security→rate_limit`
2. **Account API Keys 补种**: 为每个 super_admin 账号插入 3 条演示 API Key
3. **Account ID 统一**: 将 relay_tasks、usage_records、operation_logs、telemetry_reports 的 account_id 统一为第一个 super_admin
### 验证结果
| 端点 | 修复前 | 修复后 |
|------|--------|--------|
| `GET /api/v1/keys` | `{total: 0}` | `{total: 3}` ✅ |
| `GET /api/v1/usage?group_by=day` | `{by_day: []}` | `{total_requests: 1475, by_day: 30天}` ✅ |
| `GET /api/v1/usage?group_by=model` | `{by_model: []}` | `{by_model: 5模型}` ✅ |
| `GET /api/v1/config/items?category=general` | `{total: 0}` | `{total: 6}` ✅ |
| `GET /api/v1/config/items?category=model` | `{total: 0}` | `{total: 3}` ✅ |
| `GET /api/v1/config/items?category=rate_limit` | `{total: 0}` | `{total: 3}` ✅ |
| `GET /api/v1/relay/tasks` | 500 (类型错误) | 200 ✅ |
### 仍待修复
| Bug ID | 描述 | 原因 |
|--------|------|------|
| BUG-004 | Desktop 模型选择器卡在"加载中" | saasStore 与 configStore 数据桥未同步 |
| BUG-005 | Desktop 自动化面板崩溃 | Tauri IPC 无降级 |
---
*第二轮修复时间: 2026-03-31 00:00 CST*

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

View File

@@ -0,0 +1,173 @@
# Axum + DashMap 运行时死锁:根因分析与修复
> 2026-03-30 | 严重级别: P0 | 影响范围: SaaS 后端所有 protected route
---
## 问题描述
Admin V2 管理后台Ant Design Pro SPA浏览器访问后端时protected route 的 handler 链进入后永不返回。后端冻结health 端点也无响应,必须 kill 进程重启。
**关键特征**:
- curl 单请求测试一切正常(包括 abort 模拟)
- 浏览器访问必然触发冻结
- TimeoutLayer15s无法触发超时
- health 端点public route不经过 auth 中间件)正常返回
- protected routes经过 auth → rate_limit → request_id → api_version 中间件链)全部卡死
---
## 根本原因
### 直接原因DashMap `RefMut` 跨 `.await` 持有
`rate_limit_middleware`[middleware.rs](../../crates/zclaw-saas/src/middleware.rs))中,`DashMap::entry().or_insert_with()` 返回的 `RefMut` 持有 `parking_lot::RwLockWriteGuard`,跨越了 `next.run(req).await`
```rust
// ❌ 原始代码(有 bug
let mut entries = state.rate_limit_entries.entry(key).or_insert_with(Vec::new);
entries.retain(|&time| time > window_start);
entries.push(now);
next.run(req).await // ← RefMut 仍持有 parking_lot shard 写锁!
```
### 死锁机制
```
时间线:
T0: Worker Thread 1 → 请求 A 获取 DashMap shard X 写锁 → await DB 查询 → yield
T1: Worker Thread 2 → 请求 B 尝试获取 shard X 写锁 → parking_lot 阻塞 → Thread 2 冻结
T2: Worker Thread 3 → 请求 C 尝试获取 shard X 写锁 → parking_lot 阻塞 → Thread 3 冻结
...
TN: 所有 Worker Thread 都被 parking_lot 阻塞 → Tokio 运行时无法调度任何 task
→ TimeoutLayer 的 timeout future 无法被 poll → 超时机制失效
→ 运行时全局死锁
```
### 为什么 curl 不触发
curl 每次发送单个请求,请求完成后连接关闭。没有并发争抢同一 DashMap shard 的场景。
### 为什么浏览器触发
浏览器对同一域名默认保持 6 个并发连接HTTP/1.1 RFC 规范。SPA 页面加载时同时发起多个 API 请求dashboard stats、logs、models 等),这些请求都使用同一 JWT token → 同一 account_id → 同一 DashMap shard key。
---
## 解决方案
### 核心修复:作用域块限定 DashMap 锁
将 DashMap 操作限定在独立的作用域块 `{ ... }` 内,确保 `RefMut`(持有 parking_lot 锁)在 `next.run(req).await` 之前 drop
```rust
// ✅ 修复后代码
let blocked = {
let mut entries = state.rate_limit_entries.entry(key).or_insert_with(Vec::new);
entries.retain(|&time| time > window_start);
if entries.len() >= rate_limit {
true
} else {
entries.push(now);
false
}
}; // ← RefMut 在此处 drop释放 parking_lot shard 锁
if blocked {
return SaasError::RateLimited(...).into_response();
}
next.run(req).await // 安全:无同步锁持有
```
### 附加修复
| 修复项 | 文件 | 说明 |
|--------|------|------|
| ConnectInfo 提取 | [main.rs](../../crates/zclaw-saas/src/main.rs) | `into_make_service()``into_make_service_with_connect_info::<SocketAddr>()`,修复 login handler 的 IP 提取 |
| 移除诊断中间件 | [main.rs](../../crates/zclaw-saas/src/main.rs) | 移除 `close_connection_middleware``diag_middleware` |
| AbortSignal 链路 | [admin-v2/src/services/](../../admin-v2/src/services/) | 44 个 service 方法 + 17 个 useQuery 传入 signal页面切换时自动取消请求 |
| Vite proxy 超时 | [admin-v2/vite.config.ts](../../admin-v2/vite.config.ts) | proxy timeout 30s + configure hook |
| PG 连接池调优 | [db.rs](../../crates/zclaw-saas/src/db.rs) | max=20, min=2, acquire=5s, idle=180s |
---
## 验证结果
| 测试项 | 结果 |
|--------|------|
| Health check | 200, ~0.2s |
| Login + JWT 获取 | 200, token 正常 |
| Dashboard API | 200, ~0.23s |
| Logs API | 200, ~0.22s |
| 8 并发同端点 | 全部 200, <0.25s |
| 10 并发混合端点 | 全部 200 |
| 并发后 health check | 200, 正常 |
---
## 防范模式
### Rust async 中同步锁使用规则
**绝对禁止** `.await` 点持有任何同步锁`std::sync::Mutex``parking_lot::Mutex``parking_lot::RwLock``DashMap::Ref`/`RefMut`)。
```rust
// ❌ 危险:同步锁跨 await
let guard = some_sync_lock.lock();
do_async_work().await; // guard 仍持有!
// ✅ 安全:作用域块确保锁释放
let result = {
let guard = some_sync_lock.lock();
compute_something(&guard)
}; // guard drop
do_async_work(result).await; // 安全
```
### 何时用 tokio::sync vs parking_lot
| 场景 | 推荐 | 原因 |
|------|------|------|
| 短暂持有 .await | `parking_lot` / `DashMap` | 性能更好 async 开销 |
| 需要跨 .await 持有 | `tokio::sync::Mutex` / `RwLock` | guard Sendyield 友好 |
| 高并发读多写少 | `DashMap`但注意作用域 | 分片锁读不阻塞 |
| 需要跨 .await 持有 + 高并发 | `tokio::sync::RwLock` 或消息传递 | 避免锁竞争 |
### 代码审计清单
审计 `crates/zclaw-saas/src/` 下所有 DashMap 使用
| 文件 | 使用位置 | 状态 |
|------|----------|------|
| `middleware.rs` | `rate_limit_entries.entry()` | 已修复作用域块限定 |
| `state.rs` | `rate_limit_entries.retain()` (cleanup) | 安全同步函数无 .await |
| `state.rs` | `role_permissions_cache` 定义 | 安全只在 handlers.rs 中使用 |
| `auth/handlers.rs` | `get_role_permissions()` `.get()` | 安全guard .await drop但模式脆弱建议后续重构 |
---
## 外部参考
- [DashMap Issue #79: Deadlock in async threads](https://github.com/xacrimon/dashmap/issues/79) DashMap 维护者确认的 canonical issue
- [Beware of the DashMap Deadlock](https://www.gnunicorn.org/writings/beware-of-the-dashmap-deadlock/) 深入分析 DashMap 死锁机制
- [How to Prevent Async Deadlocks in Rust](https://savannahar68.medium.com/rust-deadlock-do-not-hold-blocking-locks-over-await-1628bf12c6d9) 同步锁跨 await 的通用模式
- [Turso: How to deadlock Tokio with just a single mutex](https://turso.tech/blog/how-to-deadlock-tokio-application-in-rust-with-just-a-single-mutex) 单锁即可死锁运行时的最小复现
- [Hyper Issue #2366: Server stops accepting connections](https://github.com/hyperium/hyper/issues/2366) Hyper 层面的连接累积问题
- [Axum Discussion #1094: Detect connection closed](https://github.com/tokio-rs/axum/discussions/1094) handler 中检测客户端断开
---
## 相关文件
| 文件 | 角色 |
|------|------|
| [crates/zclaw-saas/src/middleware.rs](../../crates/zclaw-saas/src/middleware.rs) | rate_limit_middleware 修复点 |
| [crates/zclaw-saas/src/auth/mod.rs](../../crates/zclaw-saas/src/auth/mod.rs) | auth_middleware 中间件链上层 |
| [crates/zclaw-saas/src/state.rs](../../crates/zclaw-saas/src/state.rs) | AppState 定义 DashMap 字段 |
| [crates/zclaw-saas/src/main.rs](../../crates/zclaw-saas/src/main.rs) | 路由构建TimeoutLayerConnectInfo |
| [crates/zclaw-saas/src/db.rs](../../crates/zclaw-saas/src/db.rs) | PG 连接池配置 |
| [admin-v2/src/services/request.ts](../../admin-v2/src/services/request.ts) | AbortSignal 传递工具函数 |
| [admin-v2/vite.config.ts](../../admin-v2/vite.config.ts) | Vite 代理超时配置 |

View File

@@ -141,9 +141,14 @@
| API Key 管理 | `Settings/ModelsAPI.tsx` | ✅ 通过 | 环境变量插值 `${VAR_NAME}` |
> **统计**: 共 7 个中文提供商 + 3 个国际提供商 (OpenAI/Anthropic/Gemini) + 1 个本地驱动
+ P0 安全加固 (2026-03-30)
### 7.3 其他设置
+ P0 安全加固
(2026-03-30)
| 功能 | 组件位置 | 验证状态 | 说明 |
|------|----------|----------|------|
| 技能目录 | `Settings/Skills.tsx` | ✅ 通过 | API 正常 |
@@ -162,7 +167,23 @@
| 反思日志 | `ReflectionLog` (RightPanel) | ✅ 通过 | 显示反思分析结果 |
| 安全面板 | `SecurityLayersPanel` (RightPanel) | ⚠️ 前端模拟 | 使用 fallback 数据 |
## 9. 侧边栏
## 9. P0 安全加固 (2026-03-30)
> 所有 P0 安全加固项已完成并通过 cargo check 编译验证。
| 功能 | 修改文件 | 验证状态 | 说明 |
|------|----------|----------|------|
| 密钥管理 (env 插值) | `config.rs` | ✅ 通过 | TOML 支持 `${DB_PASSWORD}` 环境变量插值 |
| JWT fallback key 保护 | `config.rs` | ✅ 通过 | `#[cfg(debug_assertions)]` 保护, release 拒绝启动 |
| Auth rate limiting (路径感知) | `middleware.rs` | ✅ 通过 | login 5次/分, register 3次/时, 其他 20次/分 |
| Logout token 撤销 | `handlers.rs` | ✅ 通过 | logout 时 DB 标记 refresh_token used_at |
| Cookie Secure 条件化 | `handlers.rs` | ✅ 通过 | `ZCLAW_SAAS_DEV=true` 时 false, 生产 true |
| TLS 终止文档 | `docs/knowledge-base/security-hardening.md` | ✅ 通过 | 生产环境必须 nginx/caddy HTTPS |
| 配置模板更新 | `saas-config.toml.example` | ✅ 通过 | 包含所有环境变量占位符说明 |
---
## 10. 侧边栏
| 功能 | 组件位置 | 验证状态 | 说明 |
|------|----------|----------|------|

View File

@@ -0,0 +1,64 @@
# P0 安全加固清单
> 审计后文档同步 — 记录每个安全加固项完成的日期和操作。
以及配置说明。
## 1. 密钥管理 — 环境变量插值
配置解析
支持 `${ENV_VAR}` 语法(如 `${DB_PASSWORD}`) 的数据库密码)。
TOMl 文件中。
无需在配置文件中明文存储密 password。
也可以通过 `ZCLAW_DATABASE_URL` 环境变量完整覆盖(优先级最高)。
### 2. JWT Fallback key 清理
配置:
- **位置**: `crates/zclaw-saas/src/config.rs`
(jwt_secret 方法)
- **状态**: 仅 debug 枝建可用 fallback`#[cfg(debug_assertions)]` 保护
(不进入 release)
- **位置**: `crates/zclaw-saas/src/auth/jwt.rs` (`TEST_SECRET`) 仅用于测试
(不暴露)
- **评估**: JWT key 的使用安全且 已好。
`config.rs` 中新增了 `interpolate_env_vars` 函数,在 `SaaSConfig::load()` 中解析 TOMl 前调用环境变量插值。
支持 `${VAR}` 语法
### 3. Auth Rate limiting (配置:
- **位置**: `crates/zclaw-saas/src/middleware.rs`
(public_rate_limit_middleware)
- **登录**: 5次/分钟/IP,注册 3次/小时/IP
縆回 login 和 refresh 20次/分钟/IP)
- **位置**: `crates/zclaw-saas/src/auth/handlers.rs` (logout handler)
- 修改 `logout` handler从仅清除 cookies 到先撤销 refresh token (DB UPDATE),再清除 cookies
Cookie Secure 标记: **条件化** (dev 模式 false, 生产模式 true)
- **位置**: `crates/zclaw-saas/src/state.rs` (cleanup_rate_limit_entries 窗口从 60s 攒大到 3600s)
- **位置**: `docs/knowledge-base/security-hardening.md` (新增)
TLS 终止文档)
- **位置**: `saas-config.toml.example`
文件已更新为包含环境变量占位符说明
生成日期: 2026-03-30
---
## 窌证状态
改动 | 文件 | 行 | 说明 | |
|------|------|----------|
| saas-config.toml ${ DB_PASSWORD} 改为 `${DB_PASSWORD}` 引用 | config 解析支持 env 插值 | ✅ 通过 |
| JWT fallback key | `config.rs` | debug 枝 不会进入 release; TEST-only ` ✅ 已安全 |
| Auth rate limiting | `middleware.rs` | login 5次/分、注册 3次/时 | ✅ 猬化 |
| Logout token 撤销 | `handlers.rs` | logout 时 DB 撤销 | ✅ 通过 |
| Cookie Secure | `handlers.rs` | 开发环境 false/生产 true | ✅ 已安全 |
| TLS 终止 | `docs/knowledge-base/security-hardening.md` | 新增文档 |
| saas-config.toml.example | 更新 | ✅ 通过 |

View File

@@ -0,0 +1,366 @@
# ZCLAW Admin V2 与 Desktop 系统定位设计
> 日期: 2026-03-30
> 状态: Draft
> 范围: Admin V2 定位 + Desktop 集成 + Token Pool + Agent 模板
## 1. 系统定位
### 1.1 Admin V2 — 资源管理与配置中心
**目标用户**: 系统管理员、运营人员
**核心职责**:
1. **账号管理** — 用户 CRUD、角色权限、LLM 路由模式配置
2. **Token 池管理** — 多提供商多密钥的统一池化管理,解决 Coding Plan 套餐的时段限额和资源浪费问题
3. **行业 Agent 模板** — 预调教好的行业 Agent 人设(含 soul.md、system_prompt、工具集供 Desktop 首次使用时获取
4. **系统监控** — 用量统计、日志审计、配置管理
### 1.2 Desktop (Agent 会话端) — 终端用户界面
**目标用户**: 终端用户AI Agent 操作者)
**核心职责**:
1. **智能对话** — 流式响应、多模型切换、上下文管理
2. **Agent 管理** — 创建、配置、切换 Agent支持从 SaaS 模板初始化)
3. **自主能力** — Hands 触发、工作流编排、技能调用
4. **智能成长** — Agent 通过对话逐步成长,记忆、反思、身份演化
### 1.3 数据流
```
Admin V2 (配置) → SaaS API → PostgreSQL
Desktop (登录) → SaaS API → 拉取 AccountProfile { llm_routing, available_templates }
┌─────────┴──────────┐
│ │
relay 模式 local 模式
│ │
SaaS Relay Tauri Kernel
│ │
Key Pool 旋转 本地 API Key
│ │
LLM Provider LLM Provider
```
---
## 2. 账号级 LLM 路由
### 2.1 背景
国内 LLM 厂商推出 Coding Plan 套餐,但套餐有时段使用量限额。每个 Desktop 客户端各自配置 API Key 会造成资源浪费。在 Admin 端搭建 Token 池,多账号共享密钥资源,成本更优。
### 2.2 数据层
`accounts` 表新增:
```sql
ALTER TABLE accounts ADD COLUMN llm_routing TEXT NOT NULL DEFAULT 'local'
CHECK (llm_routing IN ('relay', 'local'));
COMMENT ON COLUMN accounts.llm_routing IS 'LLM路由模式: relay=SaaS中转, local=本地直连';
```
默认值 `local`,保持向后兼容。
### 2.3 API 层
**登录响应扩展**:
`POST /api/v1/auth/login``GET /api/v1/auth/me` 返回值增加:
```typescript
{
token: string;
refresh_token: string;
account: {
id: string;
username: string;
role: string;
llm_routing: 'relay' | 'local'; // 新增
// ... 现有字段
}
}
```
**Admin 端账号管理**:
`PATCH /api/v1/accounts/:id` 请求体支持 `llm_routing` 字段更新,仅 `account:admin` 权限可修改。
### 2.4 Desktop 行为
`saasStore` 登录成功后读取 `account.llm_routing`,写入 `connectionStore`
- `'relay'` → 使用 `SaaSClient.chatCompletion()` 走 SaaS Relay无需启动本地 Kernel
- `'local'` → 使用 `KernelClient` 走本地 Tauri Kernel
路由模式在登录时确定,会话期间不变。切换需要管理员修改 + 用户重新登录。
---
## 3. Token Pool / Key Pool 管理
### 3.1 现状
后端 Key Pool 已完整实现:
- `select_best_key()`: 优先级排序 + RPM/TPM 滑窗检查
- `mark_key_429()`: 429 自动冷却 + `Retry-After` 支持
- `record_key_usage()`: 分钟粒度用量记录
- SSRF 防护、加密存储、降级回退
Admin UI 为只读(仅显示密钥列表),缺少添加/切换/删除操作。
### 3.2 Admin UI 补全
Providers 页面 Key Pool Modal 升级为完整管理:
| 操作 | 交互 | 说明 |
|------|------|------|
| 添加密钥 | 点击"添加"按钮 → 弹出表单 | 字段: label, key_value, priority, max_rpm(可选), max_tpm(可选) |
| 启用/禁用 | 切换 Switch 组件 | 调用 `PUT /providers/:id/keys/:keyId/toggle` |
| 删除密钥 | 点击删除 → Popconfirm 确认 | 调用 `DELETE /providers/:id/keys/:keyId` |
| 状态展示 | 表格列 | 每个密钥显示: 状态、priority、RPM/TPM 配额、当前窗口用量、总用量 |
前端使用 `providerService.addKey()`, `providerService.toggleKey()`, `providerService.deleteKey()` — 这些 service 方法已定义但未使用,直接接入即可。
### 3.3 缺陷修复
| 缺陷 | 修复方案 | 优先级 |
|------|---------|--------|
| `key_usage_window` 无清理 | Scheduler 增加定时任务,清理 >24h 的窗口记录 | P1 |
| 同优先级无负载分散 | `select_best_key()` 加入"最近最少使用"次排序 | P2 |
| `quota_reset_interval` 未使用 | 移除字段或实现按 interval 重置配额逻辑 | P3 |
---
## 4. 行业 Agent 模板系统
### 4.1 背景
通过预设行业 Agent 模板(如医疗行业),预先调教好完整的 Agent 人设。Desktop 端首次使用时从 SaaS 获取完整模板配置,之后 Agent 由用户对话驱动逐步成长,不再与模板同步。
### 4.2 数据层 — 扩展 agent_templates
```sql
ALTER TABLE agent_templates ADD COLUMN soul_content TEXT;
ALTER TABLE agent_templates ADD COLUMN scenarios TEXT NOT NULL DEFAULT '[]';
ALTER TABLE agent_templates ADD COLUMN welcome_message TEXT;
ALTER TABLE agent_templates ADD COLUMN quick_commands TEXT NOT NULL DEFAULT '[]';
ALTER TABLE agent_templates ADD COLUMN personality TEXT;
ALTER TABLE agent_templates ADD COLUMN communication_style TEXT;
ALTER TABLE agent_templates ADD COLUMN emoji TEXT;
ALTER TABLE agent_templates ADD COLUMN version INTEGER NOT NULL DEFAULT 1;
ALTER TABLE agent_templates ADD COLUMN source_id TEXT UNIQUE;
```
字段说明:
| 字段 | 类型 | 说明 |
|------|------|------|
| `soul_content` | TEXT | SOUL.md 人格配置全文 (Markdown) |
| `scenarios` | JSON | 场景标签数组,如 `["临床辅助", "文献检索"]` |
| `welcome_message` | TEXT | 首次问候语 |
| `quick_commands` | JSON | 快捷指令数组 `[{label, prompt}]` |
| `personality` | TEXT | 人格预设: professional/friendly/creative/concise |
| `communication_style` | TEXT | 沟通风格描述文本 |
| `emoji` | TEXT | Agent 图标 |
| `version` | INT | 模板版本号,用于新用户获取最新版 |
| `source_id` | TEXT | 模板唯一标识,用于 Desktop 去重 |
### 4.3 种子模板示例 — 医疗行业
```typescript
{
name: "医疗助手",
source_id: "medical-assistant-v1",
category: "healthcare",
description: "医疗行业专业 AI 助手,熟悉临床流程、医学文献和诊疗规范",
emoji: "🏥",
personality: "professional",
system_prompt: "你是一位专业的医疗AI助手具备以下能力:\n1. 临床辅助诊断建议\n2. 医学文献检索与解读\n3. 用药参考与相互作用检查\n4. 诊疗规范查询\n\n注意: 你的建议仅供参考,不能替代专业医生诊断。",
soul_content: `# SOUL.md
## 人格特质
- 专业严谨,使用准确的医学术语
- 耐心细致,对每个问题都给予充分解答
- 保守审慎,涉及诊断和用药建议时始终提示"仅供参考"
- 持续学习,关注最新医学研究进展
## 沟通风格
- 使用专业但易懂的语言
- 适当使用类比解释复杂医学概念
- 对不确定的内容明确标注置信度
- 主动提醒风险和注意事项
## 边界
- 不做明确诊断,只提供参考意见
- 不推荐处方药的具体用法用量
- 遇到紧急情况建议立即就医
`,
scenarios: ["临床辅助", "文献检索", "诊断建议", "用药参考", "病历整理"],
welcome_message: "你好我是你的医疗AI助手。我可以帮你检索文献、解读检查报告、提供诊疗参考意见。请注意我的建议仅供参考不能替代专业医生的诊断。有什么我可以帮你的吗",
quick_commands: [
{ label: "文献检索", prompt: "请帮我检索关于 {topic} 的最新医学文献" },
{ label: "用药查询", prompt: "请查询 {drug} 的药理作用、适应症和注意事项" },
{ label: "检查解读", prompt: "请帮我解读以下检查报告: {report}" },
{ label: "诊疗参考", prompt: "关于 {symptom} 的诊疗参考方案" }
],
model: "claude-sonnet-4-6",
tools: ["browser", "researcher", "collector"],
temperature: 0.3,
max_tokens: 4096,
communication_style: "使用专业但易懂的语言,适当使用类比,对不确定内容标注置信度"
}
```
### 4.4 API 层
**新增端点**:
| 端点 | 方法 | 权限 | 说明 |
|------|------|------|------|
| `/api/v1/agent-templates/available` | GET | 任何已认证 | 返回可浏览模板列表 (id, name, category, emoji, description, source_id) |
| `/api/v1/agent-templates/:id/full` | GET | 任何已认证 | 返回完整模板 (含 soul_content, welcome_message, quick_commands 等) |
**现有端点扩展**:
`POST /api/v1/agent-templates``POST /api/v1/agent-templates/:id` (update) 请求体支持所有新增字段。
### 4.5 Desktop 消费流程
```
1. 登录成功
→ saasStore 调用 GET /agent-templates/available
→ 缓存模板列表到 saasStore.availableTemplates
2. 用户创建 Agent首次 / 新建)
→ AgentOnboardingWizard 增加 Step 0: 选择行业模板
→ 展示 Grid 卡片emoji + 名称 + 分类 + 描述)
→ 两个入口: "空白 Agent" / "从模板创建"
3. 选择模板后
→ 调用 GET /agent-templates/:id/full 获取完整配置
→ 映射到 Desktop Clone + Kernel Agent:
| SaaS 字段 | Desktop 目标 |
|---------------------|-------------------------------------|
| name | Clone.name |
| emoji | Clone.emoji |
| personality | Clone.personality |
| scenarios | Clone.scenarios |
| communication_style | Clone.communicationStyle |
| model | Clone.model |
| system_prompt | → Kernel agent system_prompt |
| soul_content | → identity.updateFile('soul', ...) |
| welcome_message | → 首条消息展示 |
| quick_commands | → 本地缓存,快捷指令面板 |
| temperature | → Kernel agent config |
| max_tokens | → Kernel agent config |
| tools | → Kernel agent tools |
4. 创建完成
→ Agent 在本地成长
→ 不再与 SaaS 模板同步
→ 模板更新仅影响新用户
```
---
## 5. Admin V2 UI 变更汇总
### 5.1 Accounts 页面
- 编辑 Modal 增加 "LLM 路由模式" 字段
- 下拉选项: "SaaS 中转 (Token 池)" / "本地直连"
- 表格增加路由模式列
### 5.2 Providers 页面
- Key Pool Modal 从只读升级为完整管理
- 增加 "添加密钥" 按钮 → 表单 (label, key, priority, RPM, TPM)
- 密钥行增加启用/禁用 Switch 和删除按钮
- 增加用量统计展示
### 5.3 Agent Templates 页面
- 创建/编辑 Modal 增加新字段: emoji, personality, soul_content (大文本编辑器), scenarios (多选标签), welcome_message, quick_commands (动态列表), communication_style
- 列表增加 emoji 列和 category 筛选
- 预览功能: 展示模板效果(模拟 Agent 外观)
---
## 6. Desktop 端变更汇总
### 6.1 saasStore
- 登录响应增加 `llm_routing` 解析
- 新增 `fetchAvailableTemplates()` 方法
- 新增 `availableTemplates` 状态
### 6.2 connectionStore
- `connect()` 根据账号 `llm_routing` 选择路径
- relay 模式: 使用 `SaaSClient.chatCompletion()`
- local 模式: 保持现有 `KernelClient` 逻辑
### 6.3 AgentOnboardingWizard
- 新增 Step 0: 行业模板选择
- 模板 Grid 展示 (从 saasStore.availableTemplates 渲染)
- 选择模板后预填后续步骤默认值
### 6.4 agentStore
- 新增 `createFromTemplate(template)` 方法
- 映射模板字段到 Clone + Kernel Agent
---
## 7. 文件变更清单
### SaaS 后端 (Rust)
| 文件 | 变更 |
|------|------|
| `crates/zclaw-saas/migrations/` | 新增 migration: accounts 加 llm_routing, agent_templates 加扩展字段 |
| `crates/zclaw-saas/src/account/types.rs` | AccountInfo 增加 llm_routing 字段 |
| `crates/zclaw-saas/src/account/handlers.rs` | update_account 支持 llm_routing |
| `crates/zclaw-saas/src/auth/handlers.rs` | login/me 响应增加 llm_routing |
| `crates/zclaw-saas/src/agent_template/types.rs` | 扩展 Create/Update/Info 类型 |
| `crates/zclaw-saas/src/agent_template/service.rs` | 支持 soul_content 等新字段 |
| `crates/zclaw-saas/src/agent_template/mod.rs` | 新增 /available 和 /:id/full 路由 |
| `crates/zclaw-saas/src/relay/key_pool.rs` | 加入 LRU 次排序 |
| `crates/zclaw-saas/src/scheduler.rs` | 新增 key_usage_window 清理任务 |
| `crates/zclaw-saas/src/db.rs` | 扩展种子模板数据 |
### Admin V2 (TypeScript)
| 文件 | 变更 |
|------|------|
| `admin-v2/src/pages/Accounts.tsx` | 编辑 Modal 增加 LLM 路由模式字段 |
| `admin-v2/src/pages/Providers.tsx` | Key Pool Modal 升级为完整管理 |
| `admin-v2/src/pages/AgentTemplates.tsx` | 创建/编辑 Modal 增加扩展字段 |
| `admin-v2/src/services/providers.ts` | 接入已有的 addKey/toggleKey/deleteKey |
| `admin-v2/src/services/agent-templates.ts` | 新增 getFull 方法 |
| `admin-v2/src/types/index.ts` | 扩展类型定义 |
### Desktop (TypeScript)
| 文件 | 变更 |
|------|------|
| `desktop/src/store/saasStore.ts` | 增加 llm_routing 解析 + fetchAvailableTemplates |
| `desktop/src/store/connectionStore.ts` | connect() 按 routing 模式选择路径 |
| `desktop/src/store/agentStore.ts` | 新增 createFromTemplate() |
| `desktop/src/components/AgentOnboardingWizard.tsx` | 新增 Step 0: 模板选择 |
| `desktop/src/lib/saas-client.ts` | 新增 fetchAvailableTemplates + fetchTemplateFull |
---
## 8. 不在范围内
以下事项本次设计不涉及:
- AccountTier / 订阅等级体系
- 实时配置推送 (WebSocket)
- 模板市场 / 用户自定义模板上传
- Token 池计费 / 用量配额限制
- Desktop 端路由模式用户自选 (由 Admin 统一管控)