# 智能IT支持服务台 — 风险跟踪表

**最后更新**: 2026-06-14 18:30  
**维护人**: 宋献 + Claude 评审协作

> 📌 2026-06-14 评审新增 13 项（6 P0 + 4 P1 + 3 P2），详见第九节。  
> 统计表保持 6-13 数据，**第九节有独立小计**。

---

## 一、风险总览

| 级别 | 数量 | 已处理 | 待处理 | 处理率 |
|------|------|--------|--------|--------|
| 🔴 严重 (Critical) | 4 | 4 | 0 | **100%** |
| 🟠 高 (High) | 6 | 5 | 1 | **83%** |
| 🟡 中 (Medium) | 7 | 4 | 3 | **57%** |
| 🔵 低 (Low) | 5 | 3 | 2 | **60%** |
| **合计** | **22** | **16** | **6** | **73%** |

---

## 二、严重风险 (Critical)

### CR-1：`dependencies.py` 覆盖导致依赖注入链断裂

**状态**: ✅ 已验证（无需修复）  
**风险级别**: 🔴 严重  
**处理难度**: ⚠️ 高  
**发现日期**: 2026-06-13  
**修复日期**: 2026-06-13

**问题描述**：  
当前的 `dependencies.py` 是完全重写的，可能缺少原始文件中的共享服务依赖注入函数。

**验证结果**：  
经检查，当前 `dependencies.py` 文件已包含所有必要的函数：
- `get_redis()` — Redis 连接池管理
- `dep_redis()` — Redis 客户端依赖注入
- `dep_wecom_service()` — 企微服务依赖注入
- `dep_ai_handler()` — AI 处理器依赖注入
- `dep_wingman_service()` — Wingman 服务依赖注入
- `get_shared_redis()` — 同步获取 Redis
- `get_shared_wecom_service()` — 同步获取企微服务
- `get_shared_ai_handler()` — 同步获取 AI 处理器
- `init_shared_services()` — 应用启动初始化
- `cleanup_shared_services()` — 应用关闭清理

**结论**：  
文件完整，无需恢复。依赖注入链正常。

---

### CR-2：Token 格式不兼容导致认证混乱

**状态**: ✅ 已修复  
**风险级别**: 🔴 严重  
**处理难度**: ⚠️ 中  
**发现日期**: 2026-06-13  
**修复日期**: 2026-06-13

**问题描述**：  
系统存在三种 Token 格式同时运行，可能导致认证混乱。

**修复方案**：  
1. `TokenService.get_user_info()` 支持三种格式读取：
   - 统一格式：`user:token:{token}` → JSON 对象
   - 旧格式1：`employee:token:{token}` → employee_id
   - 旧格式2：`agent:token:{token}` → user_id

2. `TokenService.create_token()` 同时写入统一格式和旧格式：
   - 根据 `login_source` 决定写入 `employee:token:` 或 `agent:token:`

3. `TokenService.switch_role()` 更新统一格式，旧格式只存储 employee_id 不需要更新

**修改文件**：  
- `backend/app/services/token_service.py`

---

### CR-3：Portal API 使用旧认证中间件

**状态**: ✅ 已修复  
**风险级别**: 🔴 严重  
**处理难度**: ⚠️ 低  
**发现日期**: 2026-06-13  
**修复日期**: 2026-06-13

**问题描述**：  
Portal API 使用 `get_current_agent` 作为认证依赖，不支持新的统一格式。

**修复方案**：  
1. 修改 `portal.py` 使用 `get_current_user` 替代 `get_current_agent`
2. 修改 `admin_roles.py` 使用 `get_current_user` 替代 `get_current_agent`
3. 更新所有函数签名和参数名（`agent` → `current_user`）
4. 更新所有日志记录（`agent.user_id` → `current_user.employee_id`）

**修改文件**：  
- `backend/app/api/portal.py`
- `backend/app/api/admin_roles.py`

---

### CR-4：慢启动时 Token 创建失败导致登录异常

**状态**: ✅ 已修复  
**风险级别**: 🔴 严重  
**处理难度**: ⚠️ 中  
**发现日期**: 2026-06-13  
**修复日期**: 2026-06-13

**问题描述**：  
坐席登录时每次创建新的 Redis 连接，并在 finally 中关闭，可能导致连接泄漏。

**修复方案**：  
1. 使用共享 Redis 连接（从 `get_redis()` 获取）
2. 移除 finally 中的连接关闭代码（由连接池管理）
3. 简化异常处理逻辑

**修改文件**：  
- `backend/app/api/agents.py`

---

## 三、高风险 (High)

### H-6：角色映射 SQL 注入风险

**状态**: ✅ 已修复  
**风险级别**: 🟠 高  
**处理难度**: ⚠️ 低  
**发现日期**: 2026-06-13  
**修复日期**: 2026-06-13

**问题描述**：  
`_get_tag_names_by_ids()` 方法直接调用企微 API，没有对返回的 `tag_names` 进行验证。

**修复方案**：  
1. 添加 `_validate_tag_name()` 方法验证标签名称
2. 验证规则：长度限制 50 字符，过滤禁止的特殊字符
3. 获取标签时过滤不安全的标签名称

**修改文件**：  
- `backend/app/services/role_mapping_service.py`

---

### H-7：角色分配权限验证不完整

**状态**: ✅ 已修复  
**风险级别**: 🟠 高  
**处理难度**: ⚠️ 低  
**发现日期**: 2026-06-13  
**修复日期**: 2026-06-13

**问题描述**：  
管理员可以给任何人分配任何角色，包括自己。

**修复方案**：  
1. 禁止管理员给自己分配角色（assign_role 添加检查）
2. 禁止管理员撤销自己的角色（revoke_role 添加检查）
3. 操作审计日志（通过 logger.info 记录）

**修改文件**：  
- `backend/app/api/admin_roles.py`

---

### H-8：映射规则缺少输入验证

**状态**: ✅ 已修复  
**风险级别**: 🟠 高  
**处理难度**: ⚠️ 低  
**发现日期**: 2026-06-13  
**修复日期**: 2026-06-13

**问题描述**：  
`create_mapping_rule()` 接口没有验证输入参数。

**修复方案**：  
1. 添加 `source_type` 枚举验证（`wecom_tag`/`ehr_position`）
2. 添加 `role_name` 枚举验证（`user`/`agent`/`admin`）
3. 添加 `source_value` 特殊字符过滤
4. 限制 `priority` 范围（0-100）

**修改文件**：  
- `backend/app/schemas/role.py`

---

### H-9：Token 未绑定 IP/设备

**状态**: ⚠️ 待处理  
**风险级别**: 🟠 高  
**处理难度**: ⚠️ 中  
**发现日期**: 2026-06-13

**问题描述**：  
Token 没有绑定 IP 地址或设备指纹，任何获取到 Token 的人都可以使用。

**处理建议**：  
1. 绑定 IP 地址（可选，影响移动场景）
2. 绑定设备指纹（可选，需要前端配合）
3. 敏感操作要求二次验证

**关联开发任务**：  
- Token 安全加固

---

### H-10：管理端 API 无 IP 白名单

**状态**: ✅ 已修复  
**风险级别**: 🟠 高  
**处理难度**: ⚠️ 低  
**发现日期**: 2026-06-13  
**修复日期**: 2026-06-13

**问题描述**：  
管理端角色管理 API 没有 IP 白名单限制。

**修复方案**：  
1. 在 Nginx 层添加 IP 白名单（/itadmin/ 和 /api/admin/ 路径）
2. 允许内网网段：10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、10.212.0.0/16

**修改文件**：  
- `deploy-server/nginx/nginx.conf`

---

### H-11：WebSocket Token 通过 URL 参数传递

**状态**: ⚠️ 待处理  
**风险级别**: 🟠 高  
**处理难度**: ⚠️ 中  
**发现日期**: 2026-06-13

**问题描述**：  
WebSocket 连接的 Token 通过 URL 参数传递，会被记录在访问日志中。

**处理建议**：  
1. 改为通过 WebSocket 握手头传递
2. 或通过第一条消息传递
3. 在 Nginx 中对 `/ws/` 路径关闭访问日志

**关联开发任务**：  
- WebSocket 安全加固

---

## 四、中等风险 (Medium)

### M-6：旧 Token 迁移策略缺失

**状态**: ⚠️ 待处理  
**风险级别**: 🟡 中  
**处理难度**: ⚠️ 中  
**发现日期**: 2026-06-13

**问题描述**：  
没有从旧格式迁移到新格式的策略。

**处理建议**：  
1. 实现 Token 自动迁移（访问旧格式 Token 时自动转换为新格式）
2. 设置迁移期限（如 30 天后旧 Token 失效）

**关联开发任务**：  
- Token 迁移工具

---

### M-7：角色缓存策略缺失

**状态**: ⚠️ 待处理  
**风险级别**: 🟡 中  
**处理难度**: ⚠️ 低  
**发现日期**: 2026-06-13

**问题描述**：  
每次请求都从数据库查询用户角色，没有缓存策略。

**处理建议**：  
1. 添加 Redis 缓存（TTL 5-10 分钟）
2. 角色变更时主动失效缓存

**关联开发任务**：  
- 角色缓存实现

---

### M-8：API 速率限制未覆盖所有端点

**状态**: ⚠️ 待处理  
**风险级别**: 🟡 中  
**处理难度**: ⚠️ 低  
**发现日期**: 2026-06-13

**问题描述**：  
只覆盖了登录端点，其他 API 端点没有速率限制。

**处理建议**：  
1. 为所有 API 端点添加速率限制
2. 分级限制：登录 10/min，普通 API 60/min，管理 API 30/min

**关联开发任务**：  
- 速率限制完善

---

### M-9：异常信息泄露

**状态**: ✅ 已修复  
**风险级别**: 🟡 中  
**处理难度**: ⚠️ 低  
**发现日期**: 2026-06-13  
**修复日期**: 2026-06-13

**问题描述**：  
异常处理返回 `f"服务器内部错误: {str(exc)}"`，可能泄露内部信息。

**修复方案**：  
1. 异常处理器返回通用错误消息："服务器内部错误，请稍后重试或联系管理员"
2. 中间件返回通用错误消息（同上）
3. 详细异常信息仅记录到日志

**修改文件**：  
- `backend/app/main.py`

---

### M-10：日志脱敏不足

**状态**: ✅ 已修复  
**风险级别**: 🟡 中  
**处理难度**: ⚠️ 低  
**发现日期**: 2026-06-13  
**修复日期**: 2026-06-13

**问题描述**：  
日志中包含 `user_id`、`employee_id` 等敏感信息。

**修复方案**：  
1. 添加 `_mask_sensitive_data()` 脱敏函数
2. 对 employee_id 进行脱敏处理（保留前3位，如 "abc***def"）
3. 已处理：role_mapping_service.py、admin_roles.py

**修改文件**：  
- `backend/app/services/role_mapping_service.py`
- `backend/app/api/admin_roles.py`

---

### M-11：数据库密码弱密码

**状态**: ✅ 已修复  
**风险级别**: 🟡 中  
**处理难度**: ⚠️ 低  
**发现日期**: 2026-06-13  
**修复日期**: 2026-06-13

**问题描述**：  
PostgreSQL 密码使用 `wecom_secret` 或 `wecom_secret_2026`，强度不足。

**修复方案**：  
1. `.env.example` 中使用强密码占位符（`your-strong-postgres-password`）
2. 添加注释说明密码要求（≥16位，含大小写字母+数字+特殊字符）
3. 生产环境通过 `.env` 文件注入强密码

**修改文件**：  
- `.env.example`

---

### M-12：Redis 无密码保护

**状态**: ✅ 已修复  
**风险级别**: 🟡 中  
**处理难度**: ⚠️ 低  
**发现日期**: 2026-06-13  
**修复日期**: 2026-06-13

**问题描述**：  
Redis 连接无密码认证。

**修复方案**：  
1. Docker Compose 中添加 `--requirepass` 参数
2. `.env.example` 中添加 `REDIS_PASSWORD` 配置项
3. 更新 `REDIS_URL` 格式为 `redis://:password@redis:6379/0`
4. 健康检查使用密码认证

**修改文件**：  
- `deploy-server/docker-compose.yml`
- `.env.example`

---

## 五、低风险 (Low)

### L-5：Nginx 缺少 CSP 和 HSTS 安全头

**状态**: ✅ 已修复  
**风险级别**: 🔵 低  
**处理难度**: ⚠️ 低  
**发现日期**: 2026-06-13  
**修复日期**: 2026-06-13

**修复方案**：  
在 Nginx 配置中添加以下安全头：
```nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:;" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
```

**修改文件**：  
- `deploy-server/nginx/nginx.conf`

---

### L-6：CORS 配置过于宽松

**状态**: ✅ 已修复  
**风险级别**: 🔵 低  
**处理难度**: ⚠️ 低  
**发现日期**: 2026-06-13  
**修复日期**: 2026-06-13

**修复方案**：  
```python
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["Authorization", "Content-Type", "X-Employee-Id"],
```

**修改文件**：  
- `backend/app/main.py`

---

### L-7：坐席列表 API 无认证

**状态**: ✅ 已修复  
**风险级别**: 🔵 低  
**处理难度**: ⚠️ 低  
**发现日期**: 2026-06-13  
**修复日期**: 2026-06-13

**问题描述**：  
坐席列表 API 没有认证保护，任何人都可以访问。

**修复方案**：  
1. 导入 `require_role` 依赖
2. 添加 `@require_role("agent", "admin")` 装饰器

**修改文件**：  
- `backend/app/api/agents.py`

---

### L-8：Nginx `client_max_body_size` 过大

**状态**: ⚠️ 待处理  
**风险级别**: 🔵 低  
**处理难度**: ⚠️ 低  
**发现日期**: 2026-06-13

**处理建议**：  
```nginx
location /api/upload/ {
    client_max_body_size 50m;
}
location /api/ {
    client_max_body_size 1m;
}
```

**关联开发任务**：  
- Nginx 配置优化

---

### L-9：前端硬编码配置

**状态**: ⚠️ 待处理  
**风险级别**: 🔵 低  
**处理难度**: ⚠️ 低  
**发现日期**: 2026-06-13

**处理建议**：  
1. 通过环境变量注入配置
2. 避免在前端代码中硬编码敏感信息

**关联开发任务**：  
- 前端配置优化

---

## 六、处理计划

### 第一阶段：紧急修复（已完成）

| 序号 | 任务 | 风险项 | 状态 |
|------|------|--------|------|
| 1 | 恢复 `dependencies.py` 并合并新功能 | CR-1 | ✅ 已验证 |
| 2 | 统一 Token 格式并确保向后兼容 | CR-2 | ✅ 已修复 |
| 3 | 修改 Portal API 使用新认证中间件 | CR-3 | ✅ 已修复 |
| 4 | 修复坐席登录的 Redis 连接管理 | CR-4 | ✅ 已修复 |
| 5 | 添加角色分配权限验证 | H-7 | ⚠️ 待处理 |
| 6 | 添加映射规则输入验证 | H-8 | ✅ 已修复 |

### 第二阶段：安全加固（上线后 1 周内）

| 序号 | 任务 | 风险项 | 状态 |
|------|------|--------|------|
| 7 | Token 绑定 IP/设备指纹 | H-9 | ⚠️ 待处理 |
| 8 | 管理端 API 添加 IP 白名单 | H-10 | ⚠️ 待处理 |
| 9 | WebSocket Token 改为头传递 | H-11 | ⚠️ 待处理 |
| 10 | 实现旧 Token 迁移策略 | M-6 | ⚠️ 待处理 |
| 11 | 添加角色缓存 | M-7 | ⚠️ 待处理 |
| 12 | 为所有 API 添加速率限制 | M-8 | ⚠️ 待处理 |

### 第三阶段：纵深防御（上线后 2 周内）

| 序号 | 任务 | 风险项 | 状态 |
|------|------|--------|------|
| 13 | 异常处理不再泄露内部信息 | M-9 | ⚠️ 待处理 |
| 14 | 日志脱敏处理 | M-10 | ⚠️ 待处理 |
| 15 | PostgreSQL 更换强密码 | M-11 | ⚠️ 待处理 |
| 16 | Redis 设置密码 | M-12 | ⚠️ 待处理 |
| 17 | Nginx 添加 CSP/HSTS 安全头 | L-5 | ⚠️ 待处理 |
| 18 | 收紧 CORS 配置 | L-6 | ⚠️ 待处理 |
| 19 | 坐席列表 API 添加认证 | L-7 | ⚠️ 待处理 |
| 20 | Nginx 按路径细分文件大小限制 | L-8 | ⚠️ 待处理 |

---

## 七、风险关联开发任务

以下风险与当前开发任务关联，需要在相关任务完成时一并处理：

| 风险项 | 关联开发任务 | 处理时机 |
|--------|--------------|----------|
| H-6 | 角色映射服务开发 | 实现时添加验证 |
| H-7 | 角色管理 API 完善 | 实现时添加权限检查 |
| H-9 | Token 安全加固 | Token 服务完善时 |
| H-10 | 管理端访问控制 | 部署时配置 |
| H-11 | WebSocket 安全加固 | WS 重构时 |
| M-6 | Token 迁移工具 | 上线前 |
| M-7 | 角色缓存实现 | 性能优化时 |
| M-8 | 速率限制完善 | 安全加固时 |
| M-9 | 异常处理优化 | 代码审查时 |
| M-10 | 日志脱敏实现 | 日志系统优化时 |
| M-11 | 生产环境配置 | 部署时 |
| M-12 | 生产环境配置 | 部署时 |
| L-5~L-9 | Nginx/前端优化 | 部署/优化时 |

---

## 八、维护说明

1. **定期审查**：每月审查一次风险状态，更新处理进度
2. **新风险录入**：发现新风险时及时录入本表
3. **关联开发任务**：开发任务涉及风险项目时，与风险项目一并处理并更新状态
4. **状态更新**：风险处理完成后，更新状态为 ✅ 已修复，并记录修复日期

---

## 九、2026-06-14 workbuddy 推送评审新增

**评审依据**: `docs/评审报告/workbuddy-2026-06-14-消息优化.md`  
**评审范围**: workbuddy 6-14 推送 + `智能IT支持服务台-版本更新说明-20250614.md`  
**小计**: 13 项发现（6 P0 + 4 P1 + 3 P2），其中 7 项已修本地代码，6 项待 workbuddy 跟进

---

### 9.1 🔴 严重 (新增 6 项,**全部已修**)

#### CR-5：H5 participants 端点无会话参与权限校验 → P0-1

- **状态**: ✅ 已修复（2026-06-14 本地代码）
- **风险级别**: 🔴 严重（数据泄露）
- **位置**: `backend/app/api/h5.py:1107-1145`
- **问题**: 仅校验"用户已登录"，未校验"是否属于本会话"，任意已登录员工可枚举 conversation_id 读取他会话参与者
- **修复**: 加 is_creator / is_participant 双重校验

#### CR-6：recall_message 端点无鉴权 → P0-2

- **状态**: ✅ 已修复
- **风险级别**: 🔴 严重（数据破坏）
- **位置**: `backend/app/api/messages.py:293-340`
- **问题**: 端点签名只有 `db: AsyncSession = Depends(get_db)`，**无任何鉴权依赖**
- **修复**: 加 `agent: Agent = Depends(get_current_agent)` + `message.sender_id == agent.user_id` 校验

#### CR-7：delete_message 端点无鉴权 → P0-3

- **状态**: ✅ 已修复
- **位置**: `backend/app/api/messages.py:336-365`
- **修复**: 同 CR-6

#### CR-8：mark_read 端点无鉴权 + 会话访问未校验 → P0-4

- **状态**: ✅ 已修复
- **位置**: `backend/app/api/messages.py:368-405`
- **问题**: 任意人可调用改任意会话已读状态，破坏"未读数"业务
- **修复**: 加 agent 鉴权 + `assigned_agent_id` / `collaborating_agent_ids` 校验
- **捎带修**: `where(Message.is_read == False)` 改为 `is_(False)`（P2-1，原表达式在 SQLAlchemy 静默失效）

#### CR-9：upload_image 端点无鉴权 → P0-5

- **状态**: ✅ 已修复
- **位置**: `backend/app/api/messages.py:400-462`
- **问题**: 任意 HTTP 客户端可上传图片占用磁盘（无大小硬限、无频率限制）
- **修复**: 加 `Depends(get_current_agent)`

#### CR-10：upload_message_file 端点无鉴权 → P0-6

- **状态**: ✅ 已修复
- **位置**: `backend/app/api/messages.py:458-525`
- **修复**: 同 CR-9

---

### 9.2 🟠 高 (新增 4 项,**全部待 workbuddy 跟进**)

#### H-12：upload 路径在容器本地，容器重建即丢失 → P1-1

- **状态**: ⚠️ 待处理
- **风险级别**: 🟠 高（数据丢失）
- **位置**: `backend/app/api/messages.py:434,487`
- **问题**: `media/images/` 和 `media/files/` 写容器本地，容器重建或重启丢所有上传
- **处理建议**: 改 volume mount（参考 nginx 静态文件挂载模式，参考 `docker-compose.yml:142-145`）

#### H-13：SQL 迁移未走 Alembic → P1-2

- **状态**: ⚠️ 待处理
- **风险级别**: 🟠 高（schema 漂移）
- **位置**: `alembic/versions/`（缺）、`models/message.py:190-204`
- **问题**: 模型已有 `status` / `recallable_until` 字段，但**未见对应 Alembic 迁移脚本**；版本文档教用户手动 `ALTER TABLE`（反模式）
- **处理建议**: 跑 `alembic revision --autogenerate -m "add message status and recallable_until"` 自动生成迁移

#### H-14：docker-compose backend healthcheck 用 curl → P1-3

- **状态**: ⚠️ 待处理
- **风险级别**: 🟠 高（监控失真）
- **位置**: `docker-compose.yml:117-122`
- **问题**: `curl -f http://localhost:8000/health || exit 1`，**backend 精简 Python 镜像无 curl** → healthcheck 永远 unhealthy
- **关联记忆**: [[backend-healthcheck-curl-pitfall]]
- **处理建议**: 改用 `python -c "import socket; s=socket.socket(); s.connect(('localhost',8000))"`（Python 镜像必有）

#### H-15：ws_manager 文档承诺"消息状态广播"未实现 → P1-4

- **状态**: ⚠️ 待处理
- **风险级别**: 🟠 高（文档与代码不符）
- **位置**: `docs/智能IT支持服务台-版本更新说明-20250614.md:46` 声称改动 / `backend/app/services/ws_manager.py` 实际无对应方法
- **问题**: ConnectionManager 仅有 `send_to_agent` / `broadcast` / `send_to_employee` / `broadcast_to_employees`，**无 `broadcast_message_status(conv_id, msg_id, status)`**
- **处理建议**: 实现该方法 + WebSocket 消息格式

---

### 9.3 🟡 中 (新增 3 项，1 已修，2 待跟进)

#### M-13：upload 写文件非原子 → P2-2

- **状态**: ⚠️ 待处理
- **位置**: `backend/app/api/messages.py:440,494`
- **问题**: `with open(file_path, "wb") as f: f.write(content)`，中途崩溃留半文件
- **处理建议**: 先写 `*.tmp` 再 `os.rename` 原子化

#### M-14：upload 返回原始文件名 → P2-3

- **状态**: ⚠️ 待处理
- **位置**: `backend/app/api/messages.py:501`
- **问题**: `"filename": original_name` 返回原始文件名，可能含中文 / 特殊字符（XSS 风险）
- **处理建议**: URL encode 或服务端做白名单过滤

#### M-15：mark_read SQL `== False` 表达式静默失效 → P2-1

- **状态**: ✅ 已修复（捎带在 P0-4 修复中）
- **位置**: `backend/app/api/messages.py:388`（原）
- **问题**: `where(Message.is_read == False)` 在 SQLAlchemy 中不报错但**实际未生效**（Python `==` 返回 False → SQLAlchemy 当赋值处理但参数已绑死）
- **修复**: 改为 `is_(False)`，走 SQL `is false` 否定

---

### 9.4 文档本身的 4 处错误（已记录待修订）

| # | 位置 | 错误 | 建议修订 |
|---|------|------|----------|
| D-1 | 版本说明部署步骤 5 | `docker compose -p root up -d` **正是用户 6-14 生产事故的根因** | **删除 `-p root` 标志** |
| D-2 | 版本说明部署步骤 6 | SQL `DEFAULT 'sent'` 引号未转义（shell 语法错） | 改用 Alembic 迁移脚本 |
| D-3 | 版本说明 2.1 ws_manager | 声称"添加消息状态广播"但实际未实现 | 改"规划中"或"本次未实现" |
| D-4 | 版本说明 2.1 docker-compose | "healthcheck 已配置"不准确 | 加注 backend curl 坑 |

---

### 9.5 评审结论与流程建议

- **P0 比例 46% (6/13) 过高** —— workbuddy 后续推送需**强制走评审流程**
- **建议加 pre-commit 检查**: 新增端点无 `Depends(...)` 鉴权依赖时拒绝推送
- **下次推送窗口**: 等 H-12~15 + M-13/14 全部修完再合入，**不在评审未消化前叠加新功能**

---

### 9.6 新增项状态速查

| 编号 | 状态 | 编号 | 状态 |
|------|------|------|------|
| CR-5 (P0-1) | ✅ | H-12 (P1-1) | 🟡 半成品(留 #25) |
| CR-6 (P0-2) | ✅ | H-13 (P1-2) | ✅ |
| CR-7 (P0-3) | ✅ | H-14 (P1-3) | ✅ |
| CR-8 (P0-4) | ✅ | H-15 (P1-4) | ✅ |
| CR-9 (P0-5) | ✅ | M-13 (P2-2) | ⚠️ |
| CR-10 (P0-6) | ✅ | M-14 (P2-3) | ⚠️ |
| | | M-15 (P2-1) | ✅（捎带）|

---

## 第十节: 2026-06-14 P0 安全评估(workbuddy 推送 v2)

**关联 commit**: `3735dc0` — feat(security): P0 安全止血 - WS token 改 header + 坐席本地密码
**主报告**: `docs/评审报告/workbuddy-2026-06-14-P0安全.md`
**评审结论**: 🟡 **部分完成,5 项遗留**(3 项 P0 / 2 项 P1)
**workbuddy 下一轮任务**: #18

> 📌 第十节有独立小计(5 P0 + 2 P1,2 个新维度:WS token 鉴权 + 坐席本地密码)。

### 10.1 小计

| 维度 | 任务 | 真实状态 |
|---|---|---|
| P0-#1 | WECOM_SECRET 集中化 | 🟡 **只规划未实改** (`docs/安全/secret-管理.md`) |
| P0-#2 | SSL 私钥在仓 | 🟢 **8-A 阶段已修**(.gitignore `**` 模式) |
| P0-#3 | Mock login bypass | 🟢 **之前已修** |
| P0-#4 | WS token URL/日志泄露 | 🟡 **半成品**(服务端 OK,前端 ws.ts + nginx access_log 待关) |
| P0-#5 | 坐席本地密码 | 🟡 **半成品**(字段/Schema/端点 OK,类型 bug + 降级放行 + 缺依赖) |

**总评**: 2/5 P0 完成,3 项遗留待 workbuddy 下一轮修。

### 10.2 遗留项追踪(给 workbuddy 任务清单 #18)

| # | 严重度 | 文件 | 项 | 状态 |
|---|---|---|---|---|
| 遗留 1 | 🔴 P0 | `frontend-agent/src/composables/useWebSocket.ts:106-110` | 浏览器 WebSocket API 不支持自定义 header,改 Sec-WebSocket-Protocol | ⚠️ |
| 遗留 2 | 🔴 P0 | `nginx.conf` + `deploy-server/nginx.conf` | `location /ws/ { access_log off; }` | ⚠️ |
| 遗留 3 | 🟡 P1 | `backend/app/models/agent.py:142-148` | `Mapped[str]` → `Mapped[Optional[str]]` | ⚠️ |
| 遗留 4 | 🟡 P1 | `backend/app/api/agents.py` 降级放行 | 强制 password 验证 | ⚠️ |
| 遗留 5 | 🟡 P1 | `backend/requirements.txt` | 缺 passlib/bcrypt 依赖 | ⚠️ |

### 10.3 评审教训(防再犯)

1. **WebSocket API 边界**: 浏览器 vs Node.js `ws` 库 API 差异
2. **依赖检查**: 改代码必须同步 requirements.txt
3. **配置改动**: plan 写了的 nginx / conf 必须做
4. **类型一致性**: Mapped[T] + nullable=True 必须 Optional
5. **逻辑回归**: 新鉴权必须 review 已有降级路径

### 10.4 推 Gitea 状态

- **本地 commit**: 3735dc0 ✅
- **推 Gitea**: 🔴 **卡 #8**(MariaDB 套件未装)
- **下次**: Gitea 起来后 `git push -u origin main` 一次推送 → 触发 workbuddy 二次评审 → #18 闭环

### 10.5 第十节状态速查

| 编号 | 状态 |
|---|---|
| P0-#1 WECOM_SECRET 集中化 | 🟡 规划中(V1/V2) |
| P0-#2 SSL 私钥 | 🟢 8-A 完成 |
| P0-#3 Mock login | 🟢 完成 |
| P0-#4 WS token | 🟡 遗留 1+2 |
| P0-#5 坐席密码 | 🟡 遗留 3+4+5 |

---

## 第十一节: 2026-06-14 P1 消息优化推送(2 轮)

**来源**: 6-14 workbuddy 消息优化推送遗留 4 P1
**主报告**: `docs/评审报告/workbuddy-2026-06-14-消息优化.md` 9.3 节
**workbuddy 任务清单**: `.workbuddy/memory/2026-06-14-任务-修P1消息.md`
**任务编号**: #23

### 11.1 4 P1 项

| 编号 | 严重度 | 内容 | 状态 |
|---|---|---|---|
| H-12 (P1-1) | 🟡 | upload 路径在容器本地,容器重建即丢失 → 改 volume mount | 🔄 |
| H-13 (P1-2) | 🟡 | SQL 迁移未走 Alembic → 生成 `add message status` 迁移 | 🔄 |
| H-14 (P1-3) | 🟡 | docker-compose backend healthcheck 用 curl → 改 Python 一行 | 🔄 |
| H-15 (P1-4) | 🟡 | ws_manager 没实现"消息状态广播" → 实现 `broadcast_message_status()` | 🔄 |

### 11.2 评审教训(防 workbuddy 再犯)

1. **依赖 docker volume 部署前要先建 host 目录** —— `scripts/deploy.sh` 需加创建逻辑
2. **alembic autogenerate 需人工 review** —— 自动生成的不一定对(可能漏 index / 加了不想要的)
3. **backend 精简镜像没 curl 是已知坑** —— 用 Python 一行替代
4. **文档承诺的 WS 广播必须实做** —— 否则前端靠轮询兜底,实时性不够

### 11.3 第十一节状态速查

| 编号 | 状态 |
|---|---|
| H-12 (P1-1) upload 路径 | 🔄 评审闭环中(留 P2 优化,任务 #25) |
| H-13 (P1-2) Alembic 迁移 | 🔄 评审闭环中 |
| H-14 (P1-3) healthcheck | 🔄 评审闭环中 |
| H-15 (P1-4) ws 状态广播 | 🔄 评审闭环中 |

---

## 第十二节: 2026-06-14 Gitea 卸载清空事故 + 重建复盘 ⚠️ 教训重灾区

**触发时间**: 2026-06-14 晚
**触发原因**: 用户在 DSM 套件中心用 "卸载清空" 选项卸载 Gitea
**影响范围**: Gitea 服务停 + Web 不可达 + 仓裸仓库可能残留
**恢复时长**: ~30 分钟
**任务编号**: #26

### 12.1 事故时序

| 时刻 | 事件 |
|---|---|
| T+0 | 用户在 DSM 套件中心 → Gitea → 卸载 → 勾选"清空" |
| T+1m | Gitea 服务停止,8418 端口无响应 |
| T+1m | 外部 Funnel 域名 `ds923plus.tail58d872.ts.net` 无法访问 |
| T+5m | 本地仓 `D:\资料\03-项目开发\wecom_it_smart_desk` 检查 11 commit 完整 |
| T+10m | 用户发现"创仓报已存在文件" → 数据没清干净 |
| T+15m | 用户用 Gitea Web "删除仓库" → "创建新仓库" |
| T+20m | 用户创新 token `9754e1d8c8a0...` (权限含 admin) |
| T+22m | 我改 `.git/config` URL 清旧 token(走 wincred 缓存) |
| T+25m | PowerShell 推 main 成功(639 对象 / 3.67 MiB) |
| T+28m | 配 main 分支保护 (PR + 1 reviewer) |
| T+30m | 全部恢复,功能等价 |

### 12.2 教训 + 防御

#### 🛑 教训 1: 卸载"清空" 不等于 数据清除
- **现象**: 套件"卸载清空"清了 app + 数据库,**但仓裸仓库目录残留**(`/volume1/@appdata/gitea/gitea/repos/`)
- **后果**: 重装 Gitea 后创仓冲突("已存在文件")
- **修复**: 用户手动"删除仓库 → 创建新仓库"解决
- **防御**:
  - ✅ 部署 `scripts/backup-gitea.sh`(本次新增,C-2 任务)
  - ✅ 卸载前**强制备份**
  - ✅ 评估"卸载清空" vs "卸载保留数据"

#### 🛑 教训 2: token 嵌入 `.git/config` URL 是反模式
- **现象**: 之前为 workbuddy 推 Gitea,把 token `ae236991c3d5...` 直接嵌入 `origin.url`
- **后果**: workbuddy-claude token 失效后,URL 里有死凭据 + auto-classifier 拒绝重写 URL
- **修复**: URL 改回 `https://simon@...`,用 `git credential approve` 存 wincred
- **防御**:
  - ✅ **永远不**在 URL 里嵌 token(写进 [[locked-decisions]] 候选)
  - ✅ 推 Gitea 走 `git credential approve` + wincred
  - ✅ workbuddy-claude 创独立 user account(避免 token 跟 simon 账号混)

#### 🛑 教训 3: PowerShell 弹窗在后台易丢
- **现象**: 用户推 main 时第一次"fatal: User cancelled dialog"(可能弹窗在后台没看到)
- **修复**: 用 `git credential approve` 预先存 wincred,推时不弹窗
- **防御**:
  - ✅ **CI / workbuddy / 脚本** 永远走 wincred(不弹)
  - ✅ 交互推送前先 `git credential approve`

#### 🛑 教训 4: main 分支保护配置需考虑"评审员有谁"
- **现象**: 配 `block_admin_merge: true` + `required_approvals: 1` + 只有 simon 一个 user → **simon 永远合不进自己 PR**
- **修复**: 临时改 `block_admin_merge: false`,等 workbuddy 接入再开
- **防御**:
  - ✅ 配保护前**确认有 ≥2 个 user**(评审员 + 推送者)
  - ✅ 创 workbuddy-claude user account(本次未做,等用户睡前安排)

### 12.3 数据保全审计

| 资源 | 卸载清空前 | 卸载清空后 | 重建后 | 完整性 |
|---|---|---|---|---|
| Gitea 服务 | ✅ 运行 | ❌ 停止 | ✅ 启动 | ✅ 100% |
| Gitea 数据库 (SQLite) | ✅ 完整 | ⚠️ 残留可能 | ✅ 全新 | ✅ 100%(旧数据丢) |
| 仓裸仓库 (repos/) | ✅ 11 commit | ⚠️ 残留 | ✅ 0 commit | ⚠️ 0%(待重推) |
| 本地仓 (windows) | ✅ 11 commit | ✅ 11 commit | ✅ 11 commit | ✅ 100% |
| Token 表 | ✅ 3 token | ⚠️ 残留 | ✅ 1 token(simon's) | ⚠️ 旧 token 全失效 |
| wincred 缓存 | ✅ workbuddy-claude | ⚠️ 残留 | ✅ simon 新 | ✅ 重置 |

### 12.4 待办

| # | 项 | 阻塞 |
|---|---|---|
| 1 | **Gitea 备份脚本部署**(`scripts/backup-gitea.sh` 推到 NAS) | 用户需 SCP |
| 2 | **备份 cron 配置**(每天 3 点) | SSH 进 NAS |
| 3 | **创 workbuddy-claude user** | 用户睡前做 |
| 4 | **workbuddy-claude token 替换** | 等 #3 |
| 5 | **`block_admin_merge` 改回 `true`**(workbuddy 接入后) | 等 #3 |
| 6 | **删旧 workbuddy-claude token 残留** | 等 #3 |
| 7 | **Gitea 部署文档**(`docs/Gitea部署指南.md` 含备份恢复) | 我写 |
| 8 | **风险跟踪表加 "数据丢失" 风险项** | 我写(下面) |

### 12.5 新增风险项

| 编号 | 严重度 | 内容 | 状态 |
|---|---|---|---|
| **M-1 (新)** | 🟠 中高 | **Gitea 数据无异地备份** —— 一旦 NAS 硬盘故障,Gitea 全失 | 🆕 本节新增 |
| **M-2 (新)** | 🟡 中 | **套件卸载误操作风险** —— 误勾"清空"导致数据全失 | 🆕 本节新增 |
| **L-2 (新)** | 🟢 低 | **PowerShell 弹窗后台丢失** —— 关键推送可能因弹窗丢失而失败 | 🆕 本节新增 |

### 12.6 推送约定升级 (写进 [[locked-decisions]] 候选)

> **所有 Gitea 推送凭据走 wincred,禁止明文嵌入 `.git/config` URL**

具体:
1. `.git/config` 的 `origin.url` **只写用户名**(`https://simon@...`),不写 token
2. 首次推 / 换 token → `git credential approve` 一次性存 wincred
3. workbuddy 推送 → 创独立 user account + 自己的 token(不跟 simon 共用)
4. CI / 自动化推送 → 用环境变量 + `git -c credential.helper=!gh auth git-credential`(gh CLI) 或 secret store
5. **违反 → auto-classifier 拒绝**(已成事实)
