# 第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: 生产环境反向代理配置缺失