2026-07-27 11:00:21 +08:00
|
|
|
|
# 平台总后台审计报告(2026-07-27)
|
|
|
|
|
|
|
|
|
|
|
|
## 范围与结论
|
|
|
|
|
|
|
2026-07-27 12:02:08 +08:00
|
|
|
|
本次审计覆盖 46 个平台后台资源:33 个可写资源、12 个只读资源和 1 个仅追加的安全事件处置资源。后端资源契约现同时声明领域、资源名、HTTP 路径、页面类型和读写模式,并由运行时注册路由生成清单。前端审计据此逐项校验后端路由、前端资源声明、实际加载的页面组件和带菜单元数据的路由;任一层缺失均以 `领域/资源: missing <layer>` 失败。
|
|
|
|
|
|
|
2026-07-27 13:12:43 +08:00
|
|
|
|
终审追加识别的 8 项 Important 问题已全部处置并纳入回归验证:创建响应安全投影、轨迹点精确位置授权、关键字筛选一致性、表单字段类型、空可选关系、审批动作、树节点归档,以及 Biome/模型中文注释完整性。终审后两轮复核又关闭 12 项 Important:JSONB 字符串绑定、日期 RFC3339 边界、编辑密码语义、创建响应与关键字查询的显式安全白名单、服务端角色菜单授权、平台账户非 root 角色约束、默认 PII/坐标响应投影、角色菜单分配 UI,以及管理权限升级、证件附件响应、空菜单撤权和缺失菜单种子。
|
2026-07-27 11:00:21 +08:00
|
|
|
|
|
|
|
|
|
|
## 发现与处置
|
|
|
|
|
|
|
|
|
|
|
|
| 发现 | 处置 | 验证 |
|
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
|
| 只读页面的静态检查未覆盖状态变更调用 | 将 `resourceApi.updateStatus` 纳入只读页的变更操作集合,同时覆盖 create、update、archive | `audit-check.test.mjs` 的只读页用例 |
|
|
|
|
|
|
| 前端资源与后端路由只按资源名和文件名进行粗略匹配 | `ResourceContract.Path` 成为唯一后端路径声明;审计逐项校验 GET/POST/PUT/PATCH/DELETE(按资源模式)以及资源路径一致性 | Go 的 `TestEveryContractHasRegisteredRoute` 和 `pnpm audit:platform` |
|
|
|
|
|
|
| 页面存在但未被菜单路由实际加载时可能漏检 | 审计读取全部路由模块,解析页面动态导入,再确认页面通过 `getResource(契约路径)` 使用对应资源且路由具有 `menu.platform.*` 元数据 | `audit-check.test.mjs` 的菜单映射用例 |
|
|
|
|
|
|
| API 或页面可能展示、提交数据库自增 `id` / `*_id` | 对 `src/api` 与 `src/views` 全量扫描;详情页的显式过滤逻辑被识别为防护而非泄漏 | `pnpm audit:platform` |
|
2026-07-27 11:13:55 +08:00
|
|
|
|
| 仅追加处置和只读状态写入存在审计盲区 | 处置资源必须由 `saf_event` 详情动作触发、不可拥有独立页面;后端仅允许 GET/POST,且按事件标识查询处置历史;只读资源扫描 API、路由和页面中的 `updateStatus` | 6 个 `audit-check.test.mjs` 用例及逐契约路由方法测试 |
|
2026-07-27 12:19:34 +08:00
|
|
|
|
| 创建接口可能回传内部关系 ID、密码散列、地址或其他未枚举字段 | 创建成功统一经过公共身份解析和显式安全白名单,只保留 `identity`、关联 `*_identity`、状态、版本及时间元数据;地址和其他业务字段默认不返回 | `TestCreateGasAccountResolvesGasBasicIdentityBeforePersisting`、`TestCreatedResourceResponseUsesSafeAllowlist` |
|
2026-07-27 12:52:00 +08:00
|
|
|
|
| 轨迹点资源可写,且普通列表/详情可能泄露精确经纬度 | `delivery_track_point` 改为只读契约;仅 JWT 明确声明 `location_scope=precise` 时返回精确坐标,其他响应移除坐标 | `TestListDeliveryTrackPointsMasksCoordinatesWithoutPreciseLocationScope`、`TestGetDeliveryTrackPointReturnsCoordinatesWithPreciseLocationScope` |
|
|
|
|
|
|
| 受保护平台路由仅验证 JWT,非 root 账号可能访问未分配业务域,菜单接口也返回全部菜单 | JWT 后增加服务端角色菜单授权;root 放行全部业务域,非 root 仅可访问启用角色已分配菜单对应的业务域;profile 返回真实角色和菜单码,前端路由不再使用 `roles: ['*']` | `TestPlatformMenuAllowsOnlyAssignedDomain`、`TestListPlatformMenuReturnsOnlyMenusAssignedToNonRootRole`、`final-important.test.mjs` 的路由权限用例 |
|
|
|
|
|
|
| 创建平台账户未指定角色时默认成为 root,角色变更也可写入 root | 移除模型的 root 数据库默认值;创建账户强制显式指定启用的非系统角色;创建与更新都拒绝 root、缺失、停用或系统角色;前端只提供可分配角色选择 | `TestCreatePlatformAccountRequiresAssignableNonRootRole`、`pnpm type:check` |
|
|
|
|
|
|
| 通用列表与详情仅投影关联 identity,姓名、电话、头像、地址和非轨迹资源坐标可能原样返回 | 所有通用 list/detail 出口统一执行响应投影:电话和个人姓名脱敏,移除头像与地址;除显式 `location_scope=precise` 外移除所有经纬度,显式坐标授权也不放宽 PII | `TestDefaultResourceResponseMasksPIIAndCoordinates`、`TestExplicitPreciseScopeRetainsCoordinatesButStillMasksPII` |
|
|
|
|
|
|
| 角色菜单替换只有非规范复数 URL,前端没有可用入口 | 增加规范 `GET/PUT /platform/platform_role/:identity/menu`,保留原复数 PUT 兼容入口;角色详情可读取已选菜单、多选菜单 identity 并原子替换 | `TestPlatformOrganizationAndAccountRoutesExposeResourceCRUD`、`final-important.test.mjs` 的角色菜单 UI 用例 |
|
2026-07-27 13:12:43 +08:00
|
|
|
|
| 拥有 `platform` 菜单的非 root 角色可继续创建角色、菜单或改写授权,形成权限升级 | 角色与菜单写操作、角色菜单读取/替换,以及平台账户角色创建/改派均增加 root-only handler 边界;菜单状态和归档使用专用受保护 handler | `TestNonRootCannotManagePlatformRolesOrMenus`、`TestNonRootCannotAssignPlatformAccountRole` |
|
|
|
|
|
|
| 通用投影未覆盖证件编号、检查凭证和附件地址,平台账户自定义 list/detail 仍返回显示名和头像 | 集中维护敏感响应键,移除 `credential_no`、证据、文件、附件和证件 URI;平台账户自定义响应复用同一投影,显示名脱敏、头像移除,精确定位 scope 不放宽 PII | `TestPublicResponseProjectionRemovesCredentialAndAttachmentSecrets`、`TestPlatformAccountDetailMasksDisplayNameAndAvatarWithPreciseScope`、`TestPlatformAccountListMasksDisplayNameAndAvatar` |
|
|
|
|
|
|
| 角色菜单 UI 将 `menu_identities` 视为必填非空,无法撤销角色的全部菜单 | 仅该多选字段允许空数组并原样提交;其他必填字段规则不变,后端事务继续以空集合删除全部关系 | `TestReplacePlatformRoleMenusAllowsAnEmptySetToClearAssignmentsTransactionally`、`final-important.test.mjs` 的空菜单撤权用例 |
|
|
|
|
|
|
| 初始化菜单缺少受保护前端域,非 root 角色无法被授予对应入口 | 幂等种子补齐 `device`、`safety`、`content`、`notification`、`customer_service`、`audit`,并继续为 root 建立关系 | `TestInitPlatformAccessSeedsEveryProtectedFrontendDomain` |
|
2026-07-27 12:19:34 +08:00
|
|
|
|
| 通用关键字只影响列表或使用 JSONB、身份及敏感文本字段,可能导致总数与结果不一致或扩大数据暴露面 | 仅查询显式允许的安全文本列,并按模型排除个人姓名等敏感列;同一条件同时应用于 count/list | `TestListGasAccountAppliesKeywordToCountAndRows`、`TestKeywordColumnsUseSafeTextAllowlist` |
|
|
|
|
|
|
| 前端表单缺少持久化类型边界,JSONB 字符串可能变成对象,日期不能绑定 Go `time.Time` | JSON 字段在前端校验后保留字符串,后端按模型 GORM 标签将对象/数组规范化为有效 JSON 字符串并拒绝非法文本;日期与时间统一提交 RFC3339 | `TestPrepareResourceValuesNormalizesStringJSONBFields`、`final-important.test.mjs` 的 JSON 与日期用例、`pnpm type:check` |
|
|
|
|
|
|
| 创建与编辑复用密码必填规则,编辑时可能要求或误提交密码 | 密码仅在创建模式必填并进入请求体;编辑表单隐藏密码字段,载荷边界也强制忽略密码 | `final-important.test.mjs` 的创建/编辑密码语义用例 |
|
2026-07-27 12:02:08 +08:00
|
|
|
|
| 空的可选关联标识可能作为空字符串进入请求体 | 载荷边界省略空的可选 `identity` 关系,同时保留必填校验 | `final-important.test.mjs` 的空可选关系用例 |
|
|
|
|
|
|
| 审批只读页缺少同意/驳回入口 | 资源定义声明详情动作,页面按审批身份提交动作及意见并刷新详情和列表 | `final-important.test.mjs` 的审批动作回归用例 |
|
|
|
|
|
|
| 树页面只有新增/编辑,没有符合软删除约束的归档操作 | 增加二次确认,并调用资源归档接口写入 `status=archived`,不执行物理删除 | `final-important.test.mjs` 的树归档回归用例 |
|
|
|
|
|
|
| Biome 依赖 worktree 中不存在的 ignore 文件,模型字段中文注释不完整 | 禁用错误的 VCS ignore 读取;新增 Go AST 测试,要求所有导出模型字段具备中文注释 | `TestEveryModelFieldHasChineseComment`、`pnpm lint` |
|
2026-07-27 11:00:21 +08:00
|
|
|
|
|
|
|
|
|
|
## 身份字段与保留理由
|
|
|
|
|
|
|
|
|
|
|
|
- 对外 HTTP 仅使用 `identity` 及关联的 `*_identity`;数据库主键 `id` 和关系外键 `*_id` 仍用于 GORM 关联、索引和内部查询,并由响应投影剔除,不能删除。
|
|
|
|
|
|
- `status`、`version`、`created_at`、`updated_at` 是归档、乐观并发和审计所需实体元数据,予以保留。
|
|
|
|
|
|
- `product_snapshot`、`report_metric_snapshot` 保留历史商品及报表快照,避免后续主数据变更影响历史记录。
|
|
|
|
|
|
- 审计的 `before_data`、`after_data`、`operator_identity`,以及审批的 `handler_identity`、`handled_at` 用于追踪处置人和决策,不可作为冗余清除。
|
|
|
|
|
|
|
|
|
|
|
|
## 冗余清理
|
|
|
|
|
|
|
|
|
|
|
|
未删除模型字段或页面。审计没有找到同时满足“不在需求、无后端调用、无前端消费者、无测试或迁移依赖”的候选项;为避免移除潜在兼容入口,保留现有非契约文件,且不将它们纳入菜单与资源契约。
|
|
|
|
|
|
|
|
|
|
|
|
## 验证命令
|
|
|
|
|
|
|
|
|
|
|
|
以下命令在提交前执行:
|
|
|
|
|
|
|
|
|
|
|
|
```powershell
|
|
|
|
|
|
cd backend/api
|
|
|
|
|
|
go test ./...
|
|
|
|
|
|
go build ./cmd/main
|
|
|
|
|
|
|
2026-07-27 12:02:08 +08:00
|
|
|
|
cd ../..
|
2026-07-27 12:52:00 +08:00
|
|
|
|
node --test frontend/platform_admin/scripts/*.test.mjs
|
2026-07-27 12:02:08 +08:00
|
|
|
|
|
|
|
|
|
|
cd frontend/platform_admin
|
2026-07-27 11:00:21 +08:00
|
|
|
|
pnpm lint
|
2026-07-27 12:02:08 +08:00
|
|
|
|
pnpm type:check
|
2026-07-27 11:00:21 +08:00
|
|
|
|
pnpm audit:platform
|
|
|
|
|
|
pnpm build
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
| 命令 | 退出码 | 结果 |
|
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
|
| `go test ./...` | 0 | 通过 |
|
|
|
|
|
|
| `go build ./cmd/main` | 0 | 通过 |
|
2026-07-27 13:12:43 +08:00
|
|
|
|
| `node --test frontend/platform_admin/scripts/*.test.mjs` | 0 | 通过,15 个静态审计与终审回归用例;脚本按自身路径定位项目,不依赖当前工作目录 |
|
2026-07-27 12:02:08 +08:00
|
|
|
|
| `pnpm lint` | 0 | 通过;Biome 检查 169 个文件,无错误,保留 190 个非阻断 warning 和 12 个 info |
|
2026-07-27 11:00:21 +08:00
|
|
|
|
| `pnpm type:check` | 0 | 通过 |
|
|
|
|
|
|
| `pnpm audit:platform` | 0 | 通过 |
|
|
|
|
|
|
| `pnpm build` | 0 | 通过 |
|