Files
Cyrene/docs/debug_log/2026-05-20-round10-critical-fixes.md
T
AskaEth b123a36aae fix: 第四轮调试 — 回复去重/消息时序/UI布局/自主思考深度优化 + 文档重整
后端修复:
- main.go: 恢复 /api/v1/chat 路由中丢失的 handleChat 调用 (空响应回归)
- orchestrator.go: splitChatByLines 改为双换行分割, 避免单换行误拆
- chat_handler.go: multi_message 增加 !hasReview 守卫, 消息延迟 200→800ms
- thinker.go: RecordUserMessage 追踪活跃会话ID, 推送主动消息到正确会话
- thinker.go: 增强思考提示词 — 禁止在用户休息/离开时发送主动消息

前端修复:
- useWebSocket.ts: stream_segments 不再创建消息气泡, 消除重复回复
- MessageBubble.tsx: 动作消息居左对齐无头像, 时间戳移至气泡外侧 hover 显示
- ChatInput.tsx: 昔涟输入提示移至输入框上方, 波点动画效果
- MessageList/TypingIndicator/ChatContainer: 清理冗余 isTyping 传递
- MemoryPanel.tsx: 新增记忆面板组件

文档重整:
- docs/debug/ → docs/debug_log/ 重命名统一
- 新增 debug_log/README.md 索引
- .gitignore: 新增 android/ 排除规则

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 13:09:18 +08:00

182 lines
7.7 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.
# 第10轮调试修复:P0/Critical 问题修复报告
**日期**: 2026-05-20 17:56 CST
**范围**: 修复前9轮发现的最关键的 P0/Critical 安全和稳定性问题
**编译状态**: ✅ Gateway 编译通过 (exit code 0)
---
## 修复摘要
| # | 问题 | 严重级别 | 文件 | 状态 |
|---|------|---------|------|------|
| 1 | WebSocket Hub 广播循环竞态条件 | P0 | [`backend/gateway/internal/ws/hub.go`](backend/gateway/internal/ws/hub.go) | ✅ 已修复 |
| 2 | Service Worker 重复注册 | P0 | [`frontend/web/src/main.tsx`](frontend/web/src/main.tsx) | ✅ 已修复 |
| 3 | 登录/注册端点缺少速率限制 | P0/SEC-001 | [`backend/gateway/internal/router/router.go`](backend/gateway/internal/router/router.go), [`backend/gateway/internal/middleware/ratelimit.go`](backend/gateway/internal/middleware/ratelimit.go) | ✅ 已修复 |
| 4 | 记忆/知识库/提醒内容未做 HTML 转义 | P0/SEC-003 | 3 个 handler 文件 | ✅ 已修复 |
| 5 | 生产环境 Caddyfile 缺失 | P0 | 新文件 [`Caddyfile`](Caddyfile) | ✅ 已创建 |
| 6 | 用户名输入验证缺失 | P0/SEC-002 | [`backend/gateway/internal/handler/auth_handler.go`](backend/gateway/internal/handler/auth_handler.go) | ✅ 已修复 |
---
## 修复详情
### 修复-1: WebSocket Hub 广播循环竞态条件 (P0)
**文件**: [`backend/gateway/internal/ws/hub.go:238-249`](backend/gateway/internal/ws/hub.go:238)
**问题**: `Hub.Run()` 中的广播循环在 `h.mu.RLock()` (读锁) 保护下执行了写入操作:
- `delete(h.clients, client)` — 修改 map
- `close(client.Send)` — 关闭 channel
这些操作在 RLock 下执行会导致数据竞态和潜在的 panic。
**修复方案**: 两阶段广播:
1. **Phase 1**: 在 `RLock` 下遍历 `h.clients`,尝试发送消息;发送失败的客户端收集到 `staleClients` 切片
2. **Phase 2**: 在 `Lock` (写锁) 下清理失效客户端,包括:
- 二次检查客户端是否仍在 `h.clients` 中(防止已被 unregister 移除)
-`h.clients``h.userClients` 中删除
- `close(client.Send)`
- 更新 session 状态为 `"idle"`
**关键安全措施**:
- 二次检查避免 double-close client.Send
- 完整清理 userClients 索引和 session 状态
- 与 unregister 路径保持一致的清理逻辑
---
### 修复-2: Service Worker 重复注册 (P0)
**文件**: [`frontend/web/src/main.tsx:6-15`](frontend/web/src/main.tsx:6)
**问题**: Service Worker 在两个地方注册:
1. `main.tsx:9` — 直接调用 `navigator.serviceWorker.register('/sw.js')`
2. [`usePWA.ts:29`](frontend/web/src/hooks/usePWA.ts:29) — `registerServiceWorker()` 函数
两次注册产生竞态条件,可能导致 SW 更新处理混乱。
**修复方案**: 从 `main.tsx` 中移除 SW 注册代码,保留 `usePWA.ts` 中的 `registerServiceWorker()` 函数。该函数有更完善的更新处理(`updatefound` 监听、定期检查更新、SKIP_WAITING 消息处理)。
---
### 修复-3: 登录/注册端点速率限制 (P0/SEC-001)
**文件**:
- [`backend/gateway/internal/middleware/ratelimit.go`](backend/gateway/internal/middleware/ratelimit.go) — 新增 `HandlerWithKey``AuthIPKey` 方法
- [`backend/gateway/internal/router/router.go:56-62`](backend/gateway/internal/router/router.go:56) — 应用限流中间件
**问题**: 登录 (`POST /api/v1/auth/login`) 和注册 (`POST /api/v1/auth/register`) 端点没有速率限制,攻击者可暴力破解密码。
**修复方案**:
1.`ratelimit.go` 新增:
- `HandlerWithKey(keyFn)` — 按自定义 key 限流的中间件
- `AuthIPKey(endpoint)` — 返回 `"auth_{endpoint}_{clientIP}"` 作为限流 key
2.`router.go` 中为认证路由创建专用限流器:
- 速率:0.083 tokens/s + 5 burst ≈ 每分钟每 IP 每端点 5 次
- 注册和登录各使用独立的 key`auth_register_IP``auth_login_IP`
---
### 修复-4: XSS 防护 — HTML 转义 (P0/SEC-003)
**文件**:
- [`backend/gateway/internal/handler/memory_handler.go`](backend/gateway/internal/handler/memory_handler.go)
- [`backend/gateway/internal/handler/knowledge_handler.go`](backend/gateway/internal/handler/knowledge_handler.go)
- [`backend/gateway/internal/handler/reminder_handler.go`](backend/gateway/internal/handler/reminder_handler.go)
**问题**: 用户输入的内容(title、content、description、category 等)在存储时未做 HTML 转义,存在存储型 XSS 风险。
**修复方案**: 使用 Go 标准库 [`html.EscapeString()`](https://pkg.go.dev/html#EscapeString) 对所有用户提供的文本字段进行转义:
| Handler | 转义字段 |
|---------|---------|
| memory_handler.go `Add()` | `req.Content`, `req.Category` |
| knowledge_handler.go `CreateKB()` | `req.Name`, `req.Description` |
| knowledge_handler.go `UpdateKB()` | `req.Name`, `req.Description` |
| knowledge_handler.go `AddDocument()` | `req.Title`, `req.SourceType` |
| reminder_handler.go `Create()` | `req.Title`, `req.Description` |
**注意**: `html.EscapeString()``<`, `>`, `&`, `"`, `'` 转换为对应的 HTML 实体,防止 XSS 注入。转义在存储前执行,确保数据库中的数据是安全的。
---
### 修复-5: 创建生产环境 Caddyfile (P0)
**文件**: [`Caddyfile`](Caddyfile) (新建)
**问题**: [`docker-compose.yml:12`](docker-compose.yml:12) 引用了 `./Caddyfile` 但文件不存在,导致生产环境集群完全不可访问。
**修复方案**: 创建完整的 Caddyfile,包含:
- **反向代理规则**:
- `/ws/*``gateway:8080` (WebSocket 支持)
- `/api/*``gateway:8080` (HTTP API)
- 默认 → 前端占位响应
- **安全头**:
- `X-Content-Type-Options: nosniff`
- `X-Frame-Options: DENY`
- `X-XSS-Protection: 1; mode=block`
- `Referrer-Policy: strict-origin-when-cross-origin`
- **日志**: JSON 格式输出到 stdout
- **转发头**: `X-Forwarded-For`, `X-Forwarded-Proto`
---
### 修复-6: 用户名输入验证 (P0/SEC-002)
**文件**: [`backend/gateway/internal/handler/auth_handler.go`](backend/gateway/internal/handler/auth_handler.go)
**问题**: 注册时用户名接受 `user_test'; DROP TABLE users; --` 等 SQL 注入/XSS payload。
**修复方案**:
1. 添加包级别正则表达式 `usernameRegex = regexp.MustCompile(^[a-zA-Z0-9_]{3,32}$)`
2.`Register()` handler 中,JSON 绑定之后立即校验用户名格式
3.`Login()` handler 中也添加相同的用户名格式校验
4. 不符合格式返回 400: `"用户名格式无效:仅允许字母、数字和下划线,长度 3-32 位"`
虽然项目使用 `bcrypt` 和参数化查询(通过 `database/sql`),用户名格式限制仍是最佳实践,防止:
- SQL 注入 payload 作为用户名存储
- XSS payload 在其他接口展示时被执行
- 特殊字符导致的序列化/反序列化问题
---
## 编译验证
```
cd backend/gateway && go build ./...
```
**结果**: Exit code 0,无警告或错误。
---
## 影响范围
| 组件 | 影响 |
|------|------|
| Gateway | 6 个文件修改 + 编译验证 |
| Frontend | 1 个文件修改 (main.tsx) |
| 基础设施 | 1 个新文件 (Caddyfile) |
| 其他服务 | 无需修改 |
---
## 已知限制
1. **限流器**仍基于内存实现,重启后计数器丢失。生产环境建议迁移到 Redis。
2. **Caddy TLS** 配置为可选 (注释状态),需要有效的域名和端口 443 暴露。
3. **前端 SW 注册** `registerServiceWorker()` 需要在 `App.tsx` 初始化时显式调用(当前仅在 `usePWA.ts` 导出函数,未自动调用)。
---
## 相关 Issue
- SEC-001: 认证端点暴力破解防护
- SEC-002: 用户名格式校验
- SEC-003: 存储型 XSS 防护
- P0: WebSocket Hub 并发安全
- P0: Service Worker 双重注册
- P0: 生产环境反向代理配置缺失