docs: record platform access audit closure

This commit is contained in:
2026-07-27 12:52:00 +08:00
parent 160172bf10
commit 169656cc9b
3 changed files with 25 additions and 11 deletions

View File

@@ -96,8 +96,18 @@ func CurrentProfile(ctx *gin.Context) {
return return
} }
menuCodes := make([]string, 0, len(menus)) menuCodes := make([]string, 0, len(menus))
seenMenuCodes := make(map[string]bool, len(menus))
for _, menu := range menus { for _, menu := range menus {
menuCodes = append(menuCodes, menu.MenuCode) codes := []string{menu.MenuCode}
if path := strings.Trim(menu.Path, "/"); path != "" {
codes = append(codes, strings.Split(path, "/")[0])
}
for _, code := range codes {
if code != "" && !seenMenuCodes[code] {
menuCodes = append(menuCodes, code)
seenMenuCodes[code] = true
}
}
} }
infra.Response.Success(ctx, gin.H{ infra.Response.Success(ctx, gin.H{
"identity": account.Identity, "username": account.Username, "display_name": account.DisplayName, "identity": account.Identity, "username": account.Username, "display_name": account.DisplayName,

View File

@@ -5,12 +5,12 @@ import "git.apinb.com/bsm-sdk/core/database"
// PlatfromAccount 对应 platfrom_account表示平台总后台登录账号。 // PlatfromAccount 对应 platfrom_account表示平台总后台登录账号。
type PlatfromAccount struct { type PlatfromAccount struct {
Entity // 公共实体字段 Entity // 公共实体字段
Username string `gorm:"column:username;type:varchar(64);uniqueIndex;not null" json:"username"` // 登录用户名 Username string `gorm:"column:username;type:varchar(64);uniqueIndex;not null" json:"username"` // 登录用户名
DisplayName string `gorm:"column:display_name;type:varchar(64);not null;default:''" json:"display_name"` // 用户展示名称 DisplayName string `gorm:"column:display_name;type:varchar(64);not null;default:''" json:"display_name"` // 用户展示名称
Avatar string `gorm:"column:avatar;type:varchar(512);not null;default:''" json:"avatar"` // 头像资源地址 Avatar string `gorm:"column:avatar;type:varchar(512);not null;default:''" json:"avatar"` // 头像资源地址
PasswordHash string `gorm:"column:password_hash;type:varchar(255);not null;default:''" json:"-"` // 密码哈希值 PasswordHash string `gorm:"column:password_hash;type:varchar(255);not null;default:''" json:"-"` // 密码哈希值
PlatformRoleCode string `gorm:"column:platform_role_code;type:varchar(64);not null;default:'root';index" json:"platform_role_code"` // 平台角色编码 PlatformRoleCode string `gorm:"column:platform_role_code;type:varchar(64);not null;index" json:"platform_role_code"` // 平台角色编码
Phone string `gorm:"column:phone;type:varchar(32);uniqueIndex;not null;default:''" json:"phone"` // 手机号 Phone string `gorm:"column:phone;type:varchar(32);uniqueIndex;not null;default:''" json:"phone"` // 手机号
} }
func init() { database.AppendMigrate(&PlatfromAccount{}) } func init() { database.AppendMigrate(&PlatfromAccount{}) }

View File

@@ -4,7 +4,7 @@
本次审计覆盖 46 个平台后台资源33 个可写资源、12 个只读资源和 1 个仅追加的安全事件处置资源。后端资源契约现同时声明领域、资源名、HTTP 路径、页面类型和读写模式,并由运行时注册路由生成清单。前端审计据此逐项校验后端路由、前端资源声明、实际加载的页面组件和带菜单元数据的路由;任一层缺失均以 `领域/资源: missing <layer>` 失败。 本次审计覆盖 46 个平台后台资源33 个可写资源、12 个只读资源和 1 个仅追加的安全事件处置资源。后端资源契约现同时声明领域、资源名、HTTP 路径、页面类型和读写模式,并由运行时注册路由生成清单。前端审计据此逐项校验后端路由、前端资源声明、实际加载的页面组件和带菜单元数据的路由;任一层缺失均以 `领域/资源: missing <layer>` 失败。
终审追加识别的 8 项 Important 问题已全部处置并纳入回归验证:创建响应安全投影、轨迹点精确位置授权、关键字筛选一致性、表单字段类型、空可选关系、审批动作、树节点归档,以及 Biome/模型中文注释完整性。终审后复核又关闭 4 项 ImportantJSONB 字符串绑定、日期 RFC3339 边界、编辑密码语义,以及创建响应与关键字查询的显式安全白名单。 终审追加识别的 8 项 Important 问题已全部处置并纳入回归验证:创建响应安全投影、轨迹点精确位置授权、关键字筛选一致性、表单字段类型、空可选关系、审批动作、树节点归档,以及 Biome/模型中文注释完整性。终审后复核又关闭 8 项 ImportantJSONB 字符串绑定、日期 RFC3339 边界、编辑密码语义创建响应与关键字查询的显式安全白名单、服务端角色菜单授权、平台账户非 root 角色约束、默认 PII/坐标响应投影,以及角色菜单分配 UI
## 发现与处置 ## 发现与处置
@@ -16,7 +16,11 @@
| API 或页面可能展示、提交数据库自增 `id` / `*_id` | 对 `src/api``src/views` 全量扫描;详情页的显式过滤逻辑被识别为防护而非泄漏 | `pnpm audit:platform` | | API 或页面可能展示、提交数据库自增 `id` / `*_id` | 对 `src/api``src/views` 全量扫描;详情页的显式过滤逻辑被识别为防护而非泄漏 | `pnpm audit:platform` |
| 仅追加处置和只读状态写入存在审计盲区 | 处置资源必须由 `saf_event` 详情动作触发、不可拥有独立页面;后端仅允许 GET/POST且按事件标识查询处置历史只读资源扫描 API、路由和页面中的 `updateStatus` | 6 个 `audit-check.test.mjs` 用例及逐契约路由方法测试 | | 仅追加处置和只读状态写入存在审计盲区 | 处置资源必须由 `saf_event` 详情动作触发、不可拥有独立页面;后端仅允许 GET/POST且按事件标识查询处置历史只读资源扫描 API、路由和页面中的 `updateStatus` | 6 个 `audit-check.test.mjs` 用例及逐契约路由方法测试 |
| 创建接口可能回传内部关系 ID、密码散列、地址或其他未枚举字段 | 创建成功统一经过公共身份解析和显式安全白名单,只保留 `identity`、关联 `*_identity`、状态、版本及时间元数据;地址和其他业务字段默认不返回 | `TestCreateGasAccountResolvesGasBasicIdentityBeforePersisting``TestCreatedResourceResponseUsesSafeAllowlist` | | 创建接口可能回传内部关系 ID、密码散列、地址或其他未枚举字段 | 创建成功统一经过公共身份解析和显式安全白名单,只保留 `identity`、关联 `*_identity`、状态、版本及时间元数据;地址和其他业务字段默认不返回 | `TestCreateGasAccountResolvesGasBasicIdentityBeforePersisting``TestCreatedResourceResponseUsesSafeAllowlist` |
| 轨迹点资源可写,且普通列表/详情可能泄露精确经纬度 | `delivery_track_point` 改为只读契约;仅 JWT 明确声明 `location_scope=precise` 时返回精确坐标,其他响应清空坐标 | `TestListDeliveryTrackPointsMasksCoordinatesWithoutPreciseLocationScope``TestGetDeliveryTrackPointReturnsCoordinatesWithPreciseLocationScope` | | 轨迹点资源可写,且普通列表/详情可能泄露精确经纬度 | `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 用例 |
| 通用关键字只影响列表或使用 JSONB、身份及敏感文本字段可能导致总数与结果不一致或扩大数据暴露面 | 仅查询显式允许的安全文本列,并按模型排除个人姓名等敏感列;同一条件同时应用于 count/list | `TestListGasAccountAppliesKeywordToCountAndRows``TestKeywordColumnsUseSafeTextAllowlist` | | 通用关键字只影响列表或使用 JSONB、身份及敏感文本字段可能导致总数与结果不一致或扩大数据暴露面 | 仅查询显式允许的安全文本列,并按模型排除个人姓名等敏感列;同一条件同时应用于 count/list | `TestListGasAccountAppliesKeywordToCountAndRows``TestKeywordColumnsUseSafeTextAllowlist` |
| 前端表单缺少持久化类型边界JSONB 字符串可能变成对象,日期不能绑定 Go `time.Time` | JSON 字段在前端校验后保留字符串,后端按模型 GORM 标签将对象/数组规范化为有效 JSON 字符串并拒绝非法文本;日期与时间统一提交 RFC3339 | `TestPrepareResourceValuesNormalizesStringJSONBFields``final-important.test.mjs` 的 JSON 与日期用例、`pnpm type:check` | | 前端表单缺少持久化类型边界JSONB 字符串可能变成对象,日期不能绑定 Go `time.Time` | JSON 字段在前端校验后保留字符串,后端按模型 GORM 标签将对象/数组规范化为有效 JSON 字符串并拒绝非法文本;日期与时间统一提交 RFC3339 | `TestPrepareResourceValuesNormalizesStringJSONBFields``final-important.test.mjs` 的 JSON 与日期用例、`pnpm type:check` |
| 创建与编辑复用密码必填规则,编辑时可能要求或误提交密码 | 密码仅在创建模式必填并进入请求体;编辑表单隐藏密码字段,载荷边界也强制忽略密码 | `final-important.test.mjs` 的创建/编辑密码语义用例 | | 创建与编辑复用密码必填规则,编辑时可能要求或误提交密码 | 密码仅在创建模式必填并进入请求体;编辑表单隐藏密码字段,载荷边界也强制忽略密码 | `final-important.test.mjs` 的创建/编辑密码语义用例 |
@@ -46,7 +50,7 @@ go test ./...
go build ./cmd/main go build ./cmd/main
cd ../.. cd ../..
node --test frontend/platform_admin/scripts/final-important.test.mjs node --test frontend/platform_admin/scripts/*.test.mjs
cd frontend/platform_admin cd frontend/platform_admin
pnpm lint pnpm lint
@@ -59,7 +63,7 @@ pnpm build
| --- | --- | --- | | --- | --- | --- |
| `go test ./...` | 0 | 通过 | | `go test ./...` | 0 | 通过 |
| `go build ./cmd/main` | 0 | 通过 | | `go build ./cmd/main` | 0 | 通过 |
| `node --test frontend/platform_admin/scripts/final-important.test.mjs` | 0 | 通过,6 个终审回归用例;脚本按自身路径定位项目,不依赖当前工作目录 | | `node --test frontend/platform_admin/scripts/*.test.mjs` | 0 | 通过,14 个静态审计与终审回归用例;脚本按自身路径定位项目,不依赖当前工作目录 |
| `pnpm lint` | 0 | 通过Biome 检查 169 个文件,无错误,保留 190 个非阻断 warning 和 12 个 info | | `pnpm lint` | 0 | 通过Biome 检查 169 个文件,无错误,保留 190 个非阻断 warning 和 12 个 info |
| `pnpm type:check` | 0 | 通过 | | `pnpm type:check` | 0 | 通过 |
| `pnpm audit:platform` | 0 | 通过 | | `pnpm audit:platform` | 0 | 通过 |