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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions