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

7.7 KiB
Raw Blame History

第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 已修复
2 Service Worker 重复注册 P0 frontend/web/src/main.tsx 已修复
3 登录/注册端点缺少速率限制 P0/SEC-001 backend/gateway/internal/router/router.go, backend/gateway/internal/middleware/ratelimit.go 已修复
4 记忆/知识库/提醒内容未做 HTML 转义 P0/SEC-003 3 个 handler 文件 已修复
5 生产环境 Caddyfile 缺失 P0 新文件 Caddyfile 已创建
6 用户名输入验证缺失 P0/SEC-002 backend/gateway/internal/handler/auth_handler.go 已修复

修复详情

修复-1: WebSocket Hub 广播循环竞态条件 (P0)

文件: backend/gateway/internal/ws/hub.go:238-249

问题: 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.clientsh.userClients 中删除
    • close(client.Send)
    • 更新 session 状态为 "idle"

关键安全措施:

  • 二次检查避免 double-close client.Send
  • 完整清理 userClients 索引和 session 状态
  • 与 unregister 路径保持一致的清理逻辑

修复-2: Service Worker 重复注册 (P0)

文件: frontend/web/src/main.tsx:6-15

问题: Service Worker 在两个地方注册:

  1. main.tsx:9 — 直接调用 navigator.serviceWorker.register('/sw.js')
  2. usePWA.ts:29registerServiceWorker() 函数

两次注册产生竞态条件,可能导致 SW 更新处理混乱。

修复方案: 从 main.tsx 中移除 SW 注册代码,保留 usePWA.ts 中的 registerServiceWorker() 函数。该函数有更完善的更新处理(updatefound 监听、定期检查更新、SKIP_WAITING 消息处理)。


修复-3: 登录/注册端点速率限制 (P0/SEC-001)

文件:

问题: 登录 (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 次
    • 注册和登录各使用独立的 keyauth_register_IPauth_login_IP

修复-4: XSS 防护 — HTML 转义 (P0/SEC-003)

文件:

问题: 用户输入的内容(title、content、description、category 等)在存储时未做 HTML 转义,存在存储型 XSS 风险。

修复方案: 使用 Go 标准库 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 (新建)

问题: 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

问题: 注册时用户名接受 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: 生产环境反向代理配置缺失