diff --git a/docs/code-quality-remediation-plan-20260828.md b/docs/code-quality-remediation-plan-20260828.md index 0c84b6a..8cf0e2e 100644 --- a/docs/code-quality-remediation-plan-20260828.md +++ b/docs/code-quality-remediation-plan-20260828.md @@ -4,7 +4,7 @@ - 对应审计报告:`docs/code-quality-audit-20260828.md` - 当前复核基线:`c3bf8af3e6fc8bac0ac5e104f09a3d6b68506b27` - 适用范围:React/Vite 前端、NestJS API、Prisma/PostgreSQL、Redis、Go Gateway、测试与工程门禁 -- 当前结论:两个 P0 均已在当前代码中确认;P0 关闭前不得继续发布到预生产或生产 +- 执行状态(2026-08-28):P0 代码整改、自动化验证和测试环境真实 API 验证已完成;最终结果、证据与保留项见 `docs/code-quality-remediation-result-20260828.md` - 环境边界:只允许在本地和测试环境实施、部署与验证;预生产不得部署、覆盖或回退,除非再次取得明确授权 ## 1. 整改目标 diff --git a/docs/code-quality-remediation-result-20260828.md b/docs/code-quality-remediation-result-20260828.md new file mode 100644 index 0000000..1e23635 --- /dev/null +++ b/docs/code-quality-remediation-result-20260828.md @@ -0,0 +1,121 @@ +# CMPP 平台代码质量整改执行结果 + +- 执行日期:2026-08-28 +- 对应方案:`docs/code-quality-remediation-plan-20260828.md` +- 本地分支:`main` +- 测试环境:`100.93.204.60` +- 测试环境最终业务代码:`fc4a6a7afcfc3c1c11d1e353f46d5b724f48a39e` +- 环境边界:本轮只修改和部署测试环境;未访问、覆盖、回退或部署预生产 + +## 1. 结论 + +本轮已经关闭两个 P0,并完成可信租户上下文、强密码哈希、旧哈希透明迁移、Redis 验证码/锁定、重点写接口 DTO 校验、依赖升级、覆盖率和包体门禁、路由分包、ECharts 按需加载、数据库配置 fail closed、mock 类型解耦及仓库缓存治理。最终代码已部署到测试环境,95 项迁移全部一致,11 项相关服务 active,API/Gateway 健康,Redis 回执结果 Stream `pending=0、lag=0`,发布后核心服务 error 级日志为 0。 + +本轮没有把“完整治理方案”中的长期重构和全业务链复验冒充已完成:两个超大 SendChain Service 未做高风险机械拆分;未重新发送短信、执行压力测试或改动 MinIO 业务对象;浏览器完成登录页真实渲染和控制台检查,但没有在本轮自动输入测试密码和验证码完成登录后页面验收。这些项目不影响本次 P0 关闭和已发布代码运行,但仍属于后续专项验收范围。 + +## 2. 已实施变更 + +### 2.1 租户隔离 + +- 会话校验从数据库读取用户 `tenantId`,客户端请求只使用服务端可信上下文。 +- 客户端缺少租户时直接拒绝;`x-tenant-id` 仅做一致性检查,不再决定授权范围。 +- 请求头伪造租户返回 `403 / CLIENT_TENANT_MISMATCH` 并写安全事件。 +- 客户端认证、删除治理、Open API、风险审核、发送链、运营日志、短信配置、账务及用户接口均改用可信租户。 +- 客户端创建请求中的 `tenantId`、`operatorId`、`createdById` 不再作为可信身份字段;服务端覆盖或严格拒绝。 +- 应用参数、状态、密钥等按资源 ID 的操作补充租户复合范围。 + +测试环境双租户真实 API 结果: + +| 场景 | 结果 | +|---|---| +| 当前租户且不带租户请求头 | 通过 | +| 当前租户且请求头一致 | 通过 | +| 请求头伪造其他租户 | `403 CLIENT_TENANT_MISMATCH` | +| 请求体提交其他租户 ID | 被服务端强制归属当前租户 | +| 访问其他租户应用资源 ID | `404` | + +### 2.2 密码哈希 + +- 使用 Node 标准库 scrypt,格式包含版本、`N=32768/r=8/p=1`、独立 16 字节随机 salt 和摘要。 +- 验证使用恒定时间比较,并限制可接受参数范围。 +- 创建用户、改密、重置密码、生产管理员维护工具和真实环境 smoke 工具只写强哈希。 +- 旧 64 位 SHA-256 继续只读兼容;正确密码登录后使用条件更新透明升级,错误密码不迁移。 +- 首次发布使用最长 2 小时的受限兼容窗口,建立第二份恢复资产后移除兼容写入环境变量并重启 API;最终配置和 API 进程均无 `PASSWORD_HASH_LEGACY_*`。 +- 测试环境创建一次性旧 SHA-256 测试账号,经真实验证码和客户端登录接口成功迁移为 scrypt,随后硬删除;收尾 `@integration.invalid` 活跃账号为 0。 +- 测试环境仍有 4 个未登录的历史 SHA-256 账号,按透明迁移策略保留;本次没有改写未知用户密码。 + +### 2.3 登录状态与输入校验 + +- 验证码迁移到 Redis TTL,并用 `GETDEL` 原子消费。 +- 匿名失败计数使用哈希化 key 和 Lua 原子 `INCR/EXPIRE`,连续 5 次失败锁定 24 小时,成功登录清理计数。 +- Redis 不可用时 fail closed,不再退回进程内无界 Map。 +- 认证和高风险客户端写接口启用 `transform / whitelist / forbidNonWhitelisted` 严格校验;额外身份字段会被拒绝。 + +### 2.4 依赖、性能和工程门禁 + +- `react-router-dom/react-router` 升级到 `7.18.2`,NanoID 解析固定到 `3.3.18`;根生产依赖审计为 0。 +- 全部页面改为 `React.lazy` 路由级加载,新增统一加载/失败/重试边界。 +- ECharts 改为 core、chart、component、canvas renderer 按需注册。 +- 初始入口 JavaScript gzip 从审计基线约 626 KiB 降至约 107.38 KiB;图表异步块约 181.64 KiB,按首轮实测将单块预算校准为 190 KiB,入口预算 250 KiB。 +- API 覆盖率门槛建立为 statements 59%、branches 50%、functions 60%、lines 62%;本轮实际分别为 69.77%、53.47%、71.08%、73.05%。 +- 新增门禁禁止生产页面导入 mock、禁止客户端可信租户回退、禁止密码写工具使用裸 SHA-256、检查依赖版本和 bundle budget。 +- 生产/测试环境缺少 `DATABASE_URL` 时 fail closed;开发和测试模式才允许显式本地默认值。 +- `auditColumns` 业务类型移出 mock;停止跟踪 `api/tsconfig.build.tsbuildinfo` 并补充缓存/产物忽略规则。 +- `AdminDownstreamDeliveriesPage` 的展示和详情辅助逻辑已拆出。两个 SendChain 超大 Service 本轮不做无业务收益的高风险拆分,保留到专门重构任务。 + +## 3. 自动化验证 + +| 门禁 | 结果 | +|---|---| +| API Jest | 47/47 套、542/542 项通过 | +| API 覆盖率 | 69.77 / 53.47 / 71.08 / 73.05,全部超过门槛 | +| API TypeScript 正式构建 | 通过 | +| 前端 TypeScript | 通过 | +| Vite 正式构建 | 通过 | +| bundle budget | 通过 | +| Gateway `go test ./... -count=1` | 通过 | +| Gateway `go vet ./...` | 通过 | +| Prisma validate / migration | 通过,测试环境 95/95 | +| Gateway 队列契约 | 5/5 通过 | +| 代码质量/安全部署门禁 | 通过 | +| 根生产依赖审计 | 0 vulnerabilities | +| `git diff --check` | 通过 | + +API 子项目安装仍报告其依赖树中的 9 项公告(2 moderate、7 high),但原审计明确列出的 React Router 和 NanoID 两项已关闭,现有依赖缓解门禁通过。本轮没有为了追求审计数字盲目升级可能破坏 Prisma/Nest 兼容性的主版本;剩余公告应另行建立升级兼容矩阵。 + +## 4. 测试环境发布与恢复资产 + +密码格式具有单向迁移属性,因此采用两阶段发布:先发布能同时读取新旧格式且只允许受限兼容写入的版本,确认新格式写路径后重新建立恢复资产,再关闭兼容写入并发布最终版本。 + +| 恢复点 | 用途 | +|---|---| +| `/opt/cmpp-platform-backups/code-quality-before-d85ff859-20260828T035007Z` | 首次兼容发布前,旧部署 `0b843702...` | +| `/opt/cmpp-platform-backups/code-quality-compatible-d85ff859-20260828T035426Z` | 兼容版本稳定后 | +| `/opt/cmpp-platform-backups/code-quality-before-fc4a6a7-20260828T040150Z` | 最终强哈希发布前 | + +三份恢复点均包含 PostgreSQL custom dump、运行目录、配置、Redis RDB、MinIO 数据、服务/监听/Stream 状态、回退说明和 `SHA256SUMS`;`pg_restore --list`、tar 目录读取、Redis RDB 和摘要校验通过。最终发布包: + +- `outputs/cmpp-code-quality-fc4a6a7-20260828-120252.tar.gz` +- SHA-256:`7b025b31d2964e290b20104f83532ef4613929574bc3d790d584e433273841be` +- 955 个归档条目 + +## 5. 发布后验收 + +- 部署标记:`fc4a6a7afcfc3c1c11d1e353f46d5b724f48a39e`。 +- PostgreSQL:源码迁移 95,数据库完成迁移 95。 +- 服务:MinIO、Security Agent、Submit Outbox、Gateway Callback、Protocol Log Worker、Gateway、API、Send Worker、Nginx、Redis、PostgreSQL 共 11 项 active。 +- API 和 Gateway 健康响应均为 `status=ok`,测试环境入口 HTTP 200。 +- Redis `PONG`;`gateway.submit.results` 消费者 1、`pending=0、lag=0`。 +- 发布后 API、Worker、Outbox、Callback、Protocol Log Worker、Gateway、Nginx 的 error 级日志均为 0。 +- 恢复资产重新回读通过;配置与 API 进程均不存在旧哈希兼容环境变量。 +- 临时管理员、双租户 API 账号、浏览器验收账号和旧哈希迁移账号均已删除;活跃 `@integration.invalid` 用户为 0。 +- 浏览器真实打开客户端登录页,页面标题、账号/密码/验证码、验证码挑战、登录按钮和“返回官网”均可见,控制台 error/warning 为 0。 + +## 6. 保留项与边界 + +1. 登录后的客户端/运营端页面、深层路由刷新、异步加载失败和移动端状态仍需使用有效账号完成真实浏览器验收;本轮未把未登录页面或源码检查替代为登录后验收。 +2. 本轮没有重新执行短信发送、压力测试、MinIO 上传下载或 CMPP 模拟器全链;Gateway、队列和 MinIO 只做健康、状态及自动化契约验证,没有产生业务短信、计费或文件写入。 +3. 两个 SendChain 超大 Service 的职责拆分属于长期可维护性重构,应在独立提交中先补行为锁定测试后实施,不与本次安全发布混合。 +4. 工作区既有未跟踪文件 `=` 和 `pnpm-lock.yaml` 未纳入、覆盖或删除;根 `package-lock.json` 为本轮依赖门禁的标准锁文件。 +5. 测试环境通过不自动授权预生产。预生产仍必须在新的明确授权后重新建立恢复资产再发布。 + diff --git a/docs/testing-progress.md b/docs/testing-progress.md index 4d276b6..23dcf12 100644 --- a/docs/testing-progress.md +++ b/docs/testing-progress.md @@ -4094,3 +4094,13 @@ git diff --check - Chrome真实标签尝试刷新时被本机客户端策略以`ERR_BLOCKED_BY_CLIENT`拦截私网地址,因此无法把刷新后的登录态页面冒充验收证据;服务器HTTP回读和远端产物门禁用于确认新发布资源。用户侧原标签需关闭后重新打开或强制刷新以脱离旧内存资源。 - 发布前新建并校验恢复点`/opt/cmpp-platform-backups/dashboard-direct-text-20260828T023121Z`,包含PostgreSQL custom dump、原运行目录、环境/systemd/Nginx配置、原部署标记和SHA-256清单;`pg_restore --list`、两份tar目录及全部摘要校验通过。95项migration无待执行项。 - 测试环境最终部署标记与代码提交一致,新资源为`index-9yfCJZo4.css`和`index-piRXc72V.js`;远端门禁确认CSS不含旧金额类和首卡专属strong规则,JavaScript不含“余额水位”。11项相关服务active,API/Gateway健康,Gateway结果Stream`pending=0/lag=0`,发布窗口核心服务error级日志为空。仅部署测试环境`100.93.204.60`,预生产未访问或修改。 + +## 2026-08-28 代码质量 P0 整改与测试环境发布 + +- 按`docs/code-quality-remediation-plan-20260828.md`执行发布阻断整改,代码提交依次为`2744690`(可信租户、强哈希、Redis登录状态、DTO/依赖/分包/门禁)、`d85ff85`(最长2小时的回退兼容窗口)、`fc4a6a7`(管理员和smoke维护工具只写强哈希)。最终测试环境部署标记为`fc4a6a7afcfc3c1c11d1e353f46d5b724f48a39e`。 +- 真实双租户 API 矩阵通过:当前租户无header和一致header均成功;伪造其他租户header返回`403 CLIENT_TENANT_MISMATCH`;请求体tenantId被服务端覆盖为会话租户;跨租户应用资源ID返回404。一次性测试账号均清理,活跃`@integration.invalid`用户最终为0。 +- 密码使用版本化scrypt格式,旧SHA-256只读兼容并在成功登录后条件更新。测试环境一次性旧哈希账号通过真实验证码和客户端登录接口迁移为scrypt后删除;4个未登录历史旧账号保持原值等待自然迁移。兼容环境变量已从配置和API进程移除,维护脚本真实创建的临时管理员也验证为scrypt后清理。 +- API 47套542项、覆盖率69.77/53.47/71.08/73.05、前后端TypeScript/Vite构建、bundle budget、Gateway全包测试/go vet、Prisma、5份队列契约、代码质量和安全部署门禁全部通过。初始JS gzip约107.38 KiB;图表异步块约181.64 KiB,校准预算190 KiB。根生产依赖审计为0;API子项目仍有2 moderate/7 high公告,既有兼容缓解门禁通过,需后续按兼容矩阵升级。 +- 采用两阶段密码安全发布并在每次覆盖前重新建恢复资产:`code-quality-before-d85ff859-20260828T035007Z`、`code-quality-compatible-d85ff859-20260828T035426Z`、`code-quality-before-fc4a6a7-20260828T040150Z`。三份均含PostgreSQL custom dump、运行目录、配置、Redis RDB、MinIO数据、状态快照、回退说明和SHA-256;恢复清单、pg_restore、tar和Redis校验通过。 +- 最终收尾为95/95迁移、11项服务active、入口HTTP 200、API/Gateway健康、Redis PONG、`gateway.submit.results pending=0/lag=0`,发布后7项核心服务error级日志均0。浏览器真实登录页完整且控制台error/warning为0;未自动输入账号密码/验证码,登录后深层页面仍保留为人工浏览器验收边界。 +- 本轮没有发送短信、压测、修改余额/应用/通道或写入MinIO业务对象,也没有执行新的CMPP模拟器全链;两个SendChain超大Service未在安全发布中做高风险重构。完整证据和保留项见`docs/code-quality-remediation-result-20260828.md`。只操作测试环境`100.93.204.60`,预生产未访问或修改。