Skip to content

break-glass 不变量的第四条路径无守卫:删/改名 admin_full_access 那条 sys_permission_set 行,一次废掉所有 platform admin #6084

Description

@baozhoutao

发现于 #5978(第三条路径)的实现过程,范围外,未在该 PR 修

现状

packages/plugins/plugin-auth/src/last-admin-guard.tsresolveAdminUserIds 分两半枚举管理员,platform admin 那一半的第一步是:

const sets = await scan(op, SystemObjectName.PERMISSION_SET, {
  where: { name: ADMIN_FULL_ACCESS },
  fields: ['id', 'name'],
});
const adminSetIds = sets.map((r) => toId(r.id)).filter(Boolean);
if (adminSetIds.length > 0) { /* 只有这里才去读 sys_user_permission_set */ }

即「谁是 platform admin」不只依赖 sys_user_permission_set 授权行,还依赖 sys_permission_set 里那条 name = 'admin_full_access' 的行本身存在且仍叫这个名字

#5978 落地后守卫覆盖三张表(sys_user / sys_member / sys_user_permission_set),sys_permission_set 不在内。所以第四条写法仍然绕开全部守卫:

  1. 删掉那条 sys_permission_set 行;
  2. 把它的 name 改成别的值。

两者事后 adminSetIds 为空 ⇒ 所有 platform admin 的授权行还在、sys_user 行原封不动、sys_member 行原封不动,但没有任何人被枚举为 platform admin。

为什么比看上去严重

守卫有一条引导期豁免(合理且必要):

const admins = await resolveAdminUserIds(op);
if (admins.size === 0) return;   // 没有管理员可保护

所以在一个「platform admin 是唯一管理员形态」(没有 org owner/admin)的环境里,删掉那条 sys_permission_set 行之后:

  • 环境立刻进入零管理员状态;
  • 并且守卫从此对所有写放行 —— 因为 admins.size === 0 被读成「引导期,无可保护」,而不是「刚刚被清空」。

也就是说这一步不仅锁死环境,还顺带解除了 #5892 / #5941 / #5978 三条路径的守卫。

复现(engine 级)

last-admin-guard.test.ts 的既有 fixture 即可:seed 一个 platformAdmin: true 的用户 + seedAdminPermissionSet,然后

await engine.delete('sys_permission_set', { where: { id: PS_ADMIN }, ...SYSTEM });

当前 resolves(无守卫);此后 ban(engine, 'usr_platform') 也 resolves。

可能的方向(未决)

判据同样要 fail-closed。是否值得单独守一张只在部署期写一次的表,由 PM/维护者裁。

参考

Blocked-by: #5978


Generated by Claude Code

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions