Files
lislgosms/docs/code-quality-audit-20260828.md

16 KiB
Raw Permalink Blame History

CMPP 平台代码质量审查报告

  • 报告日期:2026-08-28
  • 审查类型:当前工作区只读基线审计
  • 审查仓库基线:171c7d38e8f17de0dd83570603da316d47c0d015;对应业务代码基线为其父提交 a70e9e2c078a6109dd87cf8e45aeff956faf24e8
  • 分支状态:main,相对 origin/main 超前 12 个提交
  • 审查范围:React/Vite 前端、NestJS API、Prisma/PostgreSQL、Redis/BullMQ 接口、Go Gateway、自动化测试、构建与工程配置
  • 明确未执行:提交、推送、部署、数据库写入、短信发送、远程环境变更、生产/预生产利用验证

1. 执行摘要

本次综合判定为:有条件不通过,当前版本不建议继续发布

代码已经具备较好的业务复杂度承载能力:API 45 个测试套件共 532 项全部通过,Gateway 全包测试与 go vet 通过,前后端正式构建、Prisma Schema、Gateway 队列契约和已有安全部署校验均通过;PostgreSQL 连接池、事务级 advisory lock、FOR UPDATE SKIP LOCKED 和核心业务复合索引也已实际存在。

但审查发现 2 个 P0 发布阻断问题:

  1. 客户端租户范围来自浏览器可修改的 x-tenant-id 请求头,后端多个客户端接口直接信任该值,部分写接口还直接信任请求体中的 tenantId,存在跨租户读取、创建、修改或删除业务数据的风险。
  2. 用户密码使用无盐单次 SHA-256 保存和比对。数据库一旦泄漏,相同密码可直接关联,并可使用 GPU/字典进行高速离线破解,不符合生产账号系统的密码存储要求。

因此,本报告不把“532 项测试通过”解释为生产可发布,也不把现有 mock 单测解释为真实 PostgreSQL、Redis、MinIO、Gateway 和 CMPP 全链验收完成。

2. 质量评级

维度 评级 结论
业务正确性与并发设计 B 关键发送链测试较多,存在 advisory lock、幂等和队列 claim 机制;未做本轮真实全链复验。
安全性 D 存在租户越权和弱密码哈希两个 P0。
自动化测试 C API/Gateway 测试数量较好;API 语句覆盖率 59.61%、分支覆盖率 50.99%,前端测试文件为 0,且没有覆盖率门槛。
前端性能 C- 主 JavaScript 包 2.12 MBgzip 626 KB,所有页面同步进入主包。
可维护性 C 存在超大 Service/Page、无 lint/format 门禁、生产类型仍依赖 src/mock
数据库工程 B Schema 有效,核心复合索引、连接池和锁策略较完整;未对真实数据执行 EXPLAIN (ANALYZE, BUFFERS)
依赖与仓库卫生 C- 依赖审计仍有 2 个 high advisory;构建缓存被跟踪,临时文件和产物缺少统一忽略策略。

综合参考分:58/100。该分数用于排序治理工作,不等价于功能验收通过率。

3. 发布阻断问题

CQ-SEC-001 / P0:客户端租户身份可由请求方伪造

证据链

  • src/api/session.ts:175-177 从浏览器 localStorage 中的展示会话读取 tenantId
  • src/api/core/httpClient.ts:90-92117-119 将该值写入 x-tenant-id
  • api/src/common/tenant-id.decorator.ts:3-6 直接返回客户端请求头中的 x-tenant-id,没有绑定已认证用户。
  • api/src/auth/session-validation.middleware.ts:25-55 验证会话用户、状态和 portal,但没有把用户所属租户写成服务端可信租户上下文,也没有校验请求头租户等于用户租户。
  • api/src/certification/certification.controller.ts:13-19 的客户端认证查询信任请求头,提交接口直接接受请求体。
  • api/src/certification/certification.service.ts:49-82 的提交逻辑直接使用 data.tenantId 创建认证记录、更新目标企业并写操作日志。
  • api/src/sms-config/client-sms-config.controller.ts:26-163 大量客户端查询和写接口依赖同一 @TenantId();其中应用密钥重置、应用状态变更、材料创建等接口甚至没有传入租户范围。

影响

已登录企业管理员可以修改浏览器存储、请求头、请求体或资源 ID,尝试访问其他企业的认证、应用、签名、模板、账务、日志等数据。文件服务已有“从当前会话用户反查租户”的正确实现,但该保护没有成为客户端 API 的统一规则。

最小修复要求

  1. 在服务端认证中间件中根据 sessionUserId 获取并写入可信 request.tenantId;客户端控制器只能读取该字段。
  2. 客户端路由禁止把 x-tenant-id 或请求体 tenantId 作为授权依据;即使保留请求头,也只能用于一致性检查,不得决定数据范围。
  3. 所有按资源 ID 的客户端读写在数据库查询中同时带上可信 tenantId
  4. 增加 tenant-a/tenant-b 的真实 API 越权回归,覆盖查询、创建、更新、删除、密钥重置、上传下载和导出。

CQ-SEC-002 / P0:用户密码采用无盐 SHA-256

证据链

  • api/src/users/users.service.ts:444-445createHash('sha256').update(password).digest('hex')
  • api/src/users/users.service.ts:141213267 使用该函数创建或修改密码。
  • api/src/auth/auth.service.ts:62 使用字符串相等比较验证密码。

影响

密码哈希没有用户级 salt,也没有故意增加计算和内存成本。数据库泄漏后,弱密码可被高速离线破解;相同密码生成相同哈希,还会泄露账号之间的密码复用关系。

最小修复要求

  1. 改用 Argon2id;如运行环境暂不支持,可使用带独立 salt 和合理成本参数的 scrypt/bcrypt。
  2. 新哈希保存算法版本与参数;验证旧 SHA-256 成功后立即透明升级,避免一次性强制所有账号重置。
  3. 使用库提供的恒定时间验证接口,不直接比较哈希字符串。
  4. 增加旧哈希迁移、错误密码、参数升级、密码修改和 sessionVersion 失效测试。

4. 高优先级问题

CQ-API-001 / P1API 缺少统一运行时输入校验

  • api/src/main.ts:23-27 创建应用并配置 body parser,但没有注册全局 ValidationPipe
  • 搜索未发现 @IsString@IsIntclass-validator 规则。
  • 统计到约 166 个控制器 @Body() 入口;多数 DTO 是 TypeScript interface 或内联类型,运行时会被擦除。
  • api/src/open-api/open-api.dto.ts:3-14 仅包含 Swagger 装饰器,没有输入校验装饰器。

风险包括错误类型进入服务层、超长字符串、额外字段、枚举外状态和不一致的 400/500 响应。建议建立 DTO class、transform: truewhitelist: trueforbidNonWhitelisted: true 的统一策略,并分批为高风险写接口补齐字段长度、枚举、数组大小和格式限制。

CQ-AUTH-001 / P1:验证码和匿名失败计数为进程内无界 Map

  • api/src/auth/auth.service.ts:20-21 使用两个模块级 Map
  • createCaptcha() 会持续写入记录;过期记录只有在同一 captchaId 被再次提交时才删除。
  • 匿名失败以任意登录名为 key 写入,没有容量限制或周期清理。

这会造成多实例登录状态不一致,并可通过大量验证码请求或随机登录名制造内存增长。建议迁移到 Redis,使用 TTL、原子计数、IP+账号双维度限流和固定容量保护。

CQ-FE-001 / P1:前端主包过大且没有路由级代码分割

当前生产构建结果:

  • JavaScript2,123.63 KBgzip 626.18 KB。
  • CSS270.61 KBgzip 39.84 KB。
  • Vite 明确产生“chunk 大于 500 KB”警告。
  • src/routes/AppRoutes.tsx:2-59 同步导入全部运营端和客户端页面;代码中未发现 React.lazy() 或动态 import()
  • src/components/ui/Chart.tsx:3 使用 import * as echarts from 'echarts',进一步扩大主包。

建议先按 admin/client 及页面路由使用 React.lazy + Suspense 分包,再按需引入 ECharts 图表模块。应在 CI 增加 bundle budget,例如初始 JS gzip 不高于 250 KB、单异步 chunk gzip 不高于 180 KB;实际阈值可在首轮拆包后校准。

CQ-TEST-001 / P1:测试覆盖和验收层级不足

  • API:45 套、532 项通过;全源覆盖率为 statements 59.61%、branches 50.99%、functions 60.33%、lines 62.35%。
  • api/jest.config.cjs 没有 coverageThreshold
  • src/ 下前端 *.spec.* / *.test.* 文件数量为 0。
  • Gateway 内部包覆盖率约 49.7% 至 100%,但 cmd/gateway 只有 0.7%,另两个命令包为 0%。
  • 本轮测试没有启动真实 PostgreSQL、Redis、MinIO 或 CMPP 模拟器,不能证明真实后端闭环。

建议先为租户隔离、认证、账务、发送幂等、回执关联建立真实 PostgreSQL/Redis 集成测试;前端至少覆盖登录、权限、加载/空/错误、筛选分页和高风险确认操作;随后逐步设置覆盖率门槛,避免一次性追求无意义的高百分比。

5. 中优先级问题

CQ-DEP-001 / P2:生产依赖仍有 2 个 high advisory

pnpm audit --prod --json 返回:

  1. react-router 7.18.1GHSA-qwww-vcr4-c8h2,修复版本 >=7.18.2
  2. nanoid 3.3.16GHSA-2v37-7h3g-55p8,修复版本 >=3.3.18

现有 security:verify 已证明项目没有使用 React Router RSC 模式,并验证了既有 PostCSS 缓解,因此当前可利用面低于审计工具的原始 high 评级;但版本仍处于公告范围,不能长期依赖“功能未使用”作为供应链治理。建议升级后重新执行构建、API 测试、前端浏览器回归和安全校验。

CQ-MAINT-001 / P2:超大文件与缺失静态风格门禁

  • api/src/send-chain/send-inbound-entry.service.ts 约 84 KB。
  • api/src/send-chain/send-gateway-submit.service.ts 约 52 KB。
  • src/apps/admin/AdminDownstreamDeliveriesPage.tsx 约 44 KB。
  • 根项目和 API 均没有 lint/format 脚本,也未发现 ESLint/Prettier 配置。

建议先按“持久化、状态机、队列 claim、路由、计费”拆分 send-chain 服务;页面按筛选、表格、详情和恢复操作拆分。拆分时要求行为不变,并依靠当前测试防回归。

CQ-CONFIG-001 / P2:数据库连接存在硬编码开发默认凭据

  • api/prisma.config.ts:8-11
  • api/src/prisma/prisma.service.ts:39-41

DATABASE_URL 缺失时,进程会尝试使用 cmpp/cmpp_password 连接本机数据库。建议生产角色 fail closed;仅在显式 NODE_ENV=development 或专用本地配置下允许开发默认值。

CQ-ARCH-001 / P2:真实 API 代码仍与 mock 目录耦合

  • src/mock/ 保留完整 localStorage mock service。
  • src/apps/admin/auditColumns.tsx:3 的生产组件仍从 @/mock 导入业务类型。

当前没有证据表明 mock service 仍被页面运行时调用,但该目录和类型依赖会误导后续开发,并增加重新接入静态数据的风险。建议把共享类型迁移到 src/api/types 或 domain 模块,并在构建/检查中禁止 src/apps/** 导入 src/mock/**

CQ-REPO-001 / P2:仓库产物和审计基线不稳定

  • api/tsconfig.build.tsbuildinfo 已被 Git 跟踪,每次构建产生无业务意义的修改。
  • .gitignore 没有统一忽略 *.tsbuildinfooutputs/ 和任务临时文件。
  • 最终工作区仍存在 =, outputs/, pnpm-lock.yaml, tmp_generate_ui_drafts.py 等既有未跟踪内容;审计窗口内还曾出现随后被并发流程处理的临时部署脚本。
  • 审计开始时 HEAD 为 4d4c1f3,期间其他会话先后提交了业务代码 a70e9e2 和部署记录 171c7d3;本报告已对新业务代码重新运行前端类型检查和生产构建,但完整 API 覆盖率运行发生在该纯前端提交之前。

建议统一包管理器与唯一锁文件,忽略纯构建缓存,并在正式审查/发布时使用固定 commit 或独立 worktree,避免审计结论对应移动目标。

6. 已通过门禁

检查项 结果
前端 TypeScript tsc --noEmit 通过;并发提交后已重跑
Vite 生产构建 通过;存在包体警告
API TypeScript 正式构建 通过
API Jest 45/45 套、532/532 项通过
API 覆盖率 statements 59.61%branches 50.99%functions 60.33%lines 62.35%
Gateway go test ./... -count=1 通过
Gateway go vet ./... 通过
Gateway go test ./... -cover 通过;各包覆盖率差异较大
Prisma Schema 95 个迁移目录;prisma validate 通过
Gateway 队列契约 5/5 样例通过
依赖缓解/安全部署脚本 通过
pnpm audit --prod 不通过;2 个 high advisory
git diff --check 通过

7. 积极发现

  1. PostgreSQL 连接按 API、Worker、Outbox、Callback 和 Protocol Log 角色设置独立连接池上限。
  2. 账务、日配额、频控等竞争资源使用事务级 advisory lock。
  3. 多个队列 claim 使用 FOR UPDATE SKIP LOCKED,适合并发消费者。
  4. SmsMessageRecordSmsBatchTaskCmppDownstreamDelivery 等高频表具备 tenant/status/time 复合索引。
  5. Webhook 主链对协议、内网地址、环回地址和 DNS 解析做了 SSRF 防护,并在 HTTP 客户回调中固定解析后的目标地址。
  6. 会话 cookie 使用 HttpOnly、SameSite=Lax,并按环境控制 Secure;前端 localStorage 保存的是会话展示元数据,不是 session token。
  7. API、Gateway 和队列契约测试已经覆盖大量发送、补发、回执去重和异常分支。

8. 未验证边界

本报告不能替代以下验证:

  • 真实 PostgreSQL 数据量下的 EXPLAIN (ANALYZE, BUFFERS) 和慢查询分析。
  • Redis Stream/BullMQ pending、lag、重试、宕机恢复和重复消费验证。
  • MinIO 上传、下载、租户隔离和大文件边界。
  • 登录后的真实浏览器 UI、控制台、网络请求、加载/空/错误/刷新状态。
  • Gateway 与 CMPP 模拟器或真实供应商的 Submit、长短信、回执、上行和断线恢复闭环。
  • 测试、预生产、生产当前部署 commit、迁移数、配置和服务状态。

在完成 P0 修复前,不建议通过真实环境攻击性测试证明越权;应先补自动化隔离回归,再在隔离测试环境验证。

9. 建议整改顺序

第一批:发布阻断

  1. 服务端统一可信租户上下文,关闭请求头/请求体决定客户端租户的能力。
  2. 迁移密码哈希到 Argon2id/scrypt,并提供旧哈希透明升级。
  3. 补 tenant-a/tenant-b 越权测试和密码迁移测试。

第二批:安全与质量门禁

  1. 建立全局运行时 DTO 校验。
  2. 将验证码、失败计数和限流迁移到 Redis TTL/原子计数。
  3. 升级两个公告依赖并保持安全校验通过。
  4. 在 CI 增加 lint、format check、覆盖率门槛、依赖审计和 bundle budget。

第三批:性能与可维护性

  1. 前端路由级分包并按需加载 ECharts。
  2. 拆分 send-chain 超大服务和超大页面。
  3. 清理 mock 类型耦合、构建缓存和临时产物治理。
  4. 在隔离真实后端完成 API/DB/Redis/MinIO/Gateway/CMPP 全链回归。

10. 验收出口标准

整改完成至少应满足:

  • P0 为 0,P1 有明确关闭证据或书面风险接受。
  • tenant-a 用户无法通过 header、body、query 或资源 ID 访问 tenant-b 数据。
  • 新密码使用强哈希;旧 SHA-256 账号登录后自动升级,数据库不再新增 SHA-256 密码。
  • 前端初始 JS 包达到约定预算,核心路由按需加载。
  • API/Gateway/前端测试与构建全部通过;覆盖率不低于本报告基线且建立门槛。
  • 依赖审计不再包含本报告两项 high advisory。
  • 真实 PostgreSQL、Redis、MinIO、Gateway 和 CMPP 测试证据与 docs/testing-progress.md 同步。