# UMS 项目全面代码复核报告 v6.0 **报告日期**: 2026-04-20 **审查范围**: 当前 `main` 工作区全部实现代码、旧报告未闭环问题、自动化门禁、系统化静态审查结果 **基线说明**: 本报告按日期拆分,作为 [FULL_CODE_REVIEW_REPORT_2026-04-17.md](./FULL_CODE_REVIEW_REPORT_2026-04-17.md) 的后续复核报告。凡与旧报告或旧附录冲突之处,以本报告基于 2026-04-20 新鲜命令证据和当前代码实现得到的结论为准。 --- ## 一句话结论 项目在 2026-04-17 报告中的多数首轮 P0 缺陷已经被修复,但当前代码仍存在新的认证与授权断层,且旧报告中的一部分未修复问题仍未真正闭环。当前状态不适合宣称“全部问题已修完”或“可直接上线”。 --- ## 2026-04-20 新鲜验证证据 | 项目 | 命令 | 结果 | 说明 | |---|---|---|---| | 后端构建 | `go build ./cmd/server` | PASS | 2026-04-20 23:07:51 +08:00 实跑通过 | | 后端静态检查 | `go vet ./...` | PASS | 实跑通过 | | 后端测试 | `go test ./... -count=1` | PASS | 全量通过,`internal/service` 仍是主要耗时段 | | 前端 Lint | `cd frontend/admin && npm.cmd run lint` | PASS | 与 2026-04-18 红灯状态相比已恢复 | | 前端构建 | `cd frontend/admin && npm.cmd run build` | PASS | 实跑通过 | | 系统化静态检查 | `staticcheck ./...` | FAIL | 发现测试代码 `nil context`、潜在空指针、死代码等问题 | | 安全静态检查 | `gosec ./internal/... ./cmd/...` | FAIL | 有真实问题,也有大量误报/高噪音结果,需要人工过滤 | --- ## 当前阻塞级问题 ### P0-01: `TOTP` 二次验证链路缺少首因子绑定,形成独立登录入口 **位置** - `internal/api/handler/auth_handler.go:151` - `internal/service/auth.go:125` - `internal/service/auth.go:811` **问题** - `/api/v1/auth/login/totp-verify` 只要求 `user_id + code + device_id` - 服务端 `VerifyTOTPAfterPasswordLogin()` 只校验用户状态与 `TOTP` 码,然后直接签发完整 token - 代码里虽然保留了 `TempToken` 字段,但当前登录闭环并未使用任何临时登录态或 challenge 票据 **影响** - “密码登录后第二步验证”被降级成“知道用户 ID 且拿到有效 TOTP 即可直接登录” - 这不是旧 P0-07 的原样复现,但本质上仍然属于 MFA 闭环未正确实现 **结论** - 旧报告 P0-07 不能标记为“已完全修复”,应迁移为“修复方向已变化,但认证闭环仍未完成” ### P0-02: 设备接口存在成组 `IDOR` **位置** - `internal/api/handler/device_handler.go:114` - `internal/api/handler/device_handler.go:147` - `internal/api/handler/device_handler.go:183` - `internal/api/handler/device_handler.go:214` - `internal/api/handler/device_handler.go:392` - `internal/api/handler/device_handler.go:474` - `internal/service/device.go:121` - `internal/service/device.go:158` - `internal/service/device.go:163` - `internal/service/device.go:181` - `internal/service/device.go:204` - `internal/service/device.go:236` **问题** - `GET/PUT/DELETE /devices/:id` - `PUT /devices/:id/status` - `POST/DELETE /devices/:id/trust` 这些接口的 handler 没有 owner/admin 校验,service 层也没有按 `user_id` 兜底约束,只按设备主键直接读写删除。 **影响** - 任意已登录用户只要知道设备 ID,就可以读取、修改、删除、信任或取消信任他人设备 **结论** - 这是本轮新增发现,严重程度等同发布阻塞 ### P0-03: 修改密码接口缺少“本人或管理员”授权校验 **位置** - `internal/api/handler/user_handler.go:275` - `internal/service/user_service.go:84` **问题** - `PUT /api/v1/users/:id/password` 直接使用路径里的 `id` - handler 没有 self-or-admin 校验 - service 只验证目标用户旧密码是否正确 **影响** - 普通用户在知道目标用户旧密码时可直接修改目标用户密码 - 管理员也没有单独的安全重置路径,权限模型与接口语义混杂 **结论** - 这是一条真实的授权缺口,应纳入 P0 ### P0-04: 上下文协议漂移导致多处管理员路径失效 **位置** - `internal/api/middleware/auth.go:90` - `internal/api/middleware/auth.go:91` - `internal/api/handler/user_handler.go:191` - `internal/api/handler/user_handler.go:374` - `internal/api/handler/avatar_handler.go:74` **问题** - 认证中间件当前只写入 `role_codes` / `permission_codes` - 多个 handler 仍读取旧的 `user_roles` **影响** - 管理员跨用户更新资料 - 管理员查看他人角色 - 管理员代传头像 这些路径都会被错误判定为无权限。 **结论** - 旧 P0-06 已做过一轮修复,但当前实现没有真正闭环,应以“部分修复后回归失效”迁移进新报告 ### P0-05: OAuth handler 仍返回“200 假成功”占位响应 **位置** - `internal/api/handler/auth_handler.go:316` - `internal/api/handler/auth_handler.go:329` - `internal/api/handler/auth_handler.go:342` - `internal/api/handler/auth_handler.go:353` - `internal/service/auth.go:939` - `internal/service/auth.go:946` - `internal/service/auth.go:1492` **问题** - handler 仍直接返回 `OAuth not configured` 或空 provider 列表 - service 层实际上已经存在 `OAuthLogin` / `OAuthCallback` / `GetEnabledOAuthProviders` 逻辑 **影响** - API 层向前端暴露假成功语义 - 与仓库“禁止 fake success / fail closed”的运行时规则冲突 **结论** - 这不是旧报告中的原编号问题,但属于当前实现真实性问题,应纳入高优先级修复 ### P0-06: 游标分页与动态排序的契约仍未真正闭环 **位置** - `internal/repository/user.go:353` **问题** - 当前实现只在 `sortBy == created_at` 时应用游标条件 - 其他排序字段下并不会报错,只是静默忽略游标条件 **影响** - 前端如果带着非 `created_at` 排序继续请求下一页,得到的不是严格意义上的“下一页” - 旧报告的“数据错乱”主因已经被收敛,但 API 契约仍然是不闭合的,容易出现重复页或错误分页预期 **结论** - 旧 P0-08 不应从报告中移除,应以下降风险后的“残留契约缺口”形式迁移 --- ## 从旧报告迁移的未闭环问题 下表只迁移“当前仍未真正闭环”的旧问题;已经明确修复完成的问题不再重复记为未完成。 | 旧编号 | 当前状态 | 新报告结论 | |---|---|---| | P0-06 UpdateUser IDOR | 部分修复后再次失效 | 迁移为 P0-04,上下文协议漂移导致管理员授权逻辑失效 | | P0-07 Login 绕过 TOTP | 修复方向变化,但未闭环 | 迁移为 P0-01,`totp-verify` 未绑定首因子 | | P0-08 ListCursor / sort | 风险下降但契约未闭合 | 迁移为 P0-06,非 `created_at` 排序下游标被静默忽略 | | P1-12 ~ P1-14 响应格式不一致 | 仍未修复 | 保留为 P1,`auth_handler`、`password_reset_handler` 等多处仍返回非统一响应格式 | | P2-12 `/uploads` 直接暴露 | 仍未修复 | 保留为 P2,`router.Setup()` 仍静态暴露上传目录 | --- ## 已确认修复完成的旧问题 以下问题在当前代码中已具备明确修复证据,不再迁移为“未修复项”: | 旧编号 | 当前状态 | 证据 | |---|---|---| | P0-01 LIKE 通配/模式注入 | 已修复 | `internal/repository/operation_log.go`、`internal/repository/device.go`、`internal/repository/user.go` 已统一使用 `escapeLikePattern()` | | P0-02 登录失败计数竞态 | 主路径已修复 | `internal/service/auth.go:492` 已改用 `cache.Increment()`;但降级 fallback 仍保留非原子路径,见“残留风险” | | P0-03 refresh 黑名单 fail-open | 已修复 | `internal/service/auth.go` 中黑名单写入失败已向上返回错误 | | P0-04 手机重置 replay | 基本修复 | `internal/service/password_reset.go` 在验证码校验通过后先删除 key 再继续流程 | | P0-05 CORS 默认危险组合 | 已修复 | `internal/api/middleware/cors.go` 默认值已改为空 origins + `AllowCredentials=false` | | P1-01 错误处理中间件泄露内部错误 | 已修复 | `internal/api/middleware/error.go` 对未知错误返回通用消息 | | P1-03 导出接口泄露内部错误 | 已修复 | `internal/api/handler/export_handler.go` 已改为通用错误文案 | | P1-04 CountByResultSince 静默忽略错误 | 已修复 | `internal/repository/login_log.go` 已返回 `(int64, error)` | | P1-07 Theme SetDefault 非原子 | 已修复 | `internal/repository/theme.go` 已改用事务 | | P1-08 数据库连接池硬编码 | 已修复 | `internal/database/db.go` 已使用配置参数 | | P1-15 分页参数无上限 | 大体修复 | `user_handler.go`、`device_handler.go`、`log_handler.go` 均已限制 `page_size <= 100` | --- ## 仍需保留的中高优先级问题 ### P1-01: API 响应格式仍然不统一 **位置** - `internal/api/handler/auth_handler.go` - `internal/api/handler/password_reset_handler.go` - `internal/api/handler/user_handler.go` **问题** - 同一套 API 中同时存在 `{error: ...}`、`{message: ...}`、`{code,message,data}` 等多种响应结构 - `Logout`、`CSRF`、认证错误分支、参数绑定错误分支的格式仍不一致 **影响** - 前端错误处理成本高 - 自动化契约测试难写 - 文档与真实行为容易继续漂移 ### P1-02: 登录失败计数器仍保留非原子降级路径 **位置** - `internal/service/auth.go:492` **问题** - 主路径已使用 `cache.Increment()` - 但 `Increment` 出错时仍回退到 `Get + current++ + Set` **影响** - 在缓存不支持原子递增或运行时出错场景下,旧竞态仍可能重现 **结论** - 不再按 P0 处理,但仍是必须收尾的 P1 ### P1-03: CLI/初始化路径存在权限与类型转换告警 **系统化工具证据** - `cmd/ums/cmd/init.go:306` `gosec G115` - `cmd/ums/cmd/init.go:341` `gosec G301` - `cmd/ums/cmd/init.go:446` `gosec G306` **人工判断** - `int(os.Stdin.Fd())` 在 Windows 常见运行路径下不一定形成真实高危,但应改成更明确的受控转换 - 初始化命令写目录/文件权限偏宽,适合作为 P1/P2 收敛项 ### P2-01: 上传目录仍被直接公开暴露 **位置** - `internal/api/router/router.go` **问题** - `r.engine.Static("/uploads", "./uploads")` 仍直接公开暴露上传目录 **影响** - 上传内容默认可被匿名访问 - 一旦上传内容策略控制不足,容易扩大文件暴露面 --- ## 系统化工具补充审查 ### `staticcheck ./...` 结果摘要 人工过滤后,当前值得保留的信号主要有三类: 1. **测试代码错误用法** - `internal/api/handler/captcha_handler_test.go` - `internal/service/auth_capabilities_test.go` 存在 `SA1012`,测试里向需要 `context.Context` 的调用传了 `nil`。 2. **测试代码潜在空指针** - `internal/service/sms_provider_test.go` - `internal/service/user_roles_test.go` - `internal/service/webhook_service_test.go` 存在 `SA5011`,说明部分测试断言路径缺少空值保护。 3. **仓库内死代码/遗留辅助代码** - `internal/api/middleware/auth.go` - `internal/monitoring/slo.go` - `internal/repository/sql_scan.go` - `internal/repository/pagination.go` 存在 `U1000`,说明最近几轮修复后有未清理的遗留函数或字段。 ### `gosec ./internal/... ./cmd/...` 结果摘要 `gosec` 本轮输出噪音较大,尤其把 OAuth URL、常量名、header 名、token URL 大量误判为“硬编码凭证”。人工过滤后,建议保留的结果如下: 1. **真实可收敛问题** - `internal/api/handler/avatar_handler.go:147` `G301` - `internal/api/handler/avatar_handler.go:159` `G306` - `internal/service/password_reset.go:237` - `internal/service/password_reset.go:252` 前者是目录/文件权限偏宽,后者是关键删除操作忽略返回错误。 2. **低风险但建议修整** - `internal/service/captcha.go:164` `G404` 这里使用 `math/rand` 仅用于验证码图片背景色随机化,不直接影响验证码秘密值,但可以考虑改为更明确的非安全随机用途注释,或避免被安全扫描反复报警。 3. **高噪音误报,不建议直接据此立项** - OAuth token URL / auth URL - Header 名称 - 非凭证字符串常量 这些不应直接写进缺陷列表,否则会污染修复优先级。 --- ## 当前建议修复顺序 ### 第一批:立即处理 1. 修复 `totp-verify` 登录闭环,要求必须携带首因子验证后的临时态 2. 为设备接口补全 owner/admin 校验,并在 service 层增加按 `user_id` 的兜底约束 3. 为 `/users/:id/password` 增加 self-or-admin 授权,并区分“本人修改密码”和“管理员重置密码”语义 4. 统一 handler 上下文字段,彻底移除 `user_roles` 旧协议 5. 去掉 OAuth handler 的假成功返回,改成真实能力分发或显式 fail closed ### 第二批:本周内收口 1. 统一 API 响应结构 2. 清理登录失败计数器 fallback 竞态 3. 清理 `staticcheck` 暴露的测试错误与死代码 4. 收敛 `gosec` 中目录/文件权限与关键错误忽略问题 --- ## 对旧报告的处理建议 1. 保留旧报告作为历史记录,不删除 2. 明确以本报告作为后续复核基线 3. 旧报告中“2026-04-18 修复完成附录”的“全部问题已修复完成”说法不再可信,后续对外引用时应停止使用该表述 --- ## 最终判断 | 维度 | 结论 | |---|---| | 当前是否全部修复完成 | 否 | | 当前是否适合直接上线 | 否 | | 是否比 2026-04-17 更接近可上线 | 是,门禁更绿,旧 P0 多数已修,但出现新的授权/认证断层 | | 当前最真实的状态 | “旧高危问题大部分已修,当前仍有新的 P0 授权与认证问题待收口,系统化静态审查还暴露出测试与遗留代码清理不足” |