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.ts 的 resolveAdminUserIds 分两半枚举管理员,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

  1. claude commented on Aug 6, 2026

    @claude
    Contributor

    分诊:入队(pm:queue)+ 暂阻(pm:blocked,正文已补 Blocked-by: #5978)+ 域 domain:identity。

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. self-assigned this
    on Aug 7, 2026
  3. baozhoutao commented on Aug 7, 2026

    @baozhoutao
    ContributorAuthor

    解锁 + 认领(identity 车道 PM,session_01JwwiU9bjhwy2SWj13ho8uv,第 12 轮,2026-08-07 01:3xZ)

    Blocked-by: #5978 已解除 —— PR #6086 于 01:0xZ 合入 main(93929c200)。按分诊对照查询实测复核:enforceStanding / applyPending / PendingStandingWrite 在 origin/main 的 last-admin-guard.ts 现 13 处命中(此前零命中),standing 机制已在位;引导期豁免仍在(行号漂移至 :657)。摘 pm:blocked。


    Generated by Claude Code

  4. baozhoutao commented on Aug 7, 2026

    @baozhoutao
    ContributorAuthor

    ACCEPT(identity 车道 PM,session_01JwwiU9bjhwy2SWj13ho8uv,第 12 轮收取,2026-08-07 03:0xZ)—— PR #6107,①②两方向全交付,零返工纪录保持。

    审查要点(全量 diff 核过):

    • ①(第四张表):resolveAdminUserIds 第一段 scan 改显式循环 —— applyPending 后重测 name(删行→跳过、改名→行在含义没了),与 grant 半边重测 permission_set_id 严格同构;静态跳过键仅 ['name'](枚举只读这一列,Setup 编辑 label/权限 JSON、projection 回填、os meta resync 一次读都不花);两钩子同 packageId/priority 20,八钩子作为一个 package 绑定。
    • ②(引导期豁免收紧):判据 = 悬空的无组织范围、有效期内授权行,在两处豁免点统一调用,仅零管理员分支运行(热路径零成本);$nin 空集陷阱与 NULL-safe 语义均按 非否定路径上的 $ne / $nin / $notContains:driver-sql 排除 NULL 行,driver-memory / formula 返回它们(#5146 只裁定了 $not) #5298 处理并内存重测;fresh-install 不变按构造证明(悬空行在正常路径写不出来,bootstrapPlatformAdmin 先集后授)+ 真引导期实测钉。红线处置正确:更宽的「无 admin_full_access 行 + 存在无组织授权行」方案因会改变 seed 顺序特例的答案被主动否决 —— 这正是红线条款要买的判断;残留(守卫未注册期落下的改名)已如实写进头注释。
    • 放大缺陷回归钉双向夹逼(在位守卫仍有效 + wipedEnvironment 迟注册场景全拒且报文点名证据),与无守卫基线配对,②的贡献被单独隔离。
    • dev 自证伪一次(头注释关于 system_permissions 清空的推断,实测 hasPlatformAdminGrant 只看 name,已改为记录实测而非立 finding)—— 处置正确。
    • 与 fix(plugin-auth): 短信日配额拒发时 OTP / 邀请短信回 429,不再是 500 + 空响应体 (#6039) #6092 文件面零交集(机械核对);唯一 CI 红 Temporal Conformance 为 docker registry 超时(零代码执行),随 update-branch 重触发洗掉。

    落地:ready + update-branch(base 落后 main 3 commit)+ auto-merge。本单落地后版本 PR #4935 将自动刷新收编 changeset,车道随即执行 rc.4 切版(维护者已批)。


    Generated by Claude Code

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