Repository navigation
break-glass 不变量的第四条路径无守卫:删/改名 admin_full_access 那条 sys_permission_set 行,一次废掉所有 platform admin #6084
Copy link
Copy link
Closed
Labels
Description
Activity
分诊:入队(
pm:queue)+ 暂阻(pm:blocked,正文已补Blocked-by: #5978)+ 域domain:identity。- 落点锚定:修复落在
packages/plugins/plugin-auth/src/last-admin-guard.ts(把 standing 守卫多认一张sys_permission_set,或收紧:396的admins.size === 0引导期豁免)——packages/plugins/plugin-auth⇒ 域表domain:identity行。不按标题里的「break-glass / 不变量」词汇猜,按包判。 - 为什么先 blocked(硬前提,本轮实测于
origin/main9e3709a):正文首选方向要复用的enforceStanding/applyPending/PendingStandingWrite在 main 上 零命中(阳性对照:同文件resolveAdminUserIds2 命中 ⇒ 扫描器与路径成立,零命中不是假读数)。这套 standing 机制由在飞的 break-glass 不变量的第三条路径无守卫:撤掉最后一个管理员的「身份」(sys_member 降级 / 删 admin_full_access 授权)同样锁死环境 #5978 引入 —— identity 车道 PM 已于 19:15Z 认领(分支claude/issue-5978-break-glass-third-path,申报文件面正是last-admin-guard.ts+ 其测试)。同文件、同机制、同不变量,提前入队等于让两个 dev 改同一个函数。break-glass 不变量的第三条路径无守卫:撤掉最后一个管理员的「身份」(sys_member 降级 / 删 admin_full_access 授权)同样锁死环境 #5978 的 PR 合入即摘pm:blocked。 - 不判
needs-user-decision的理由:与 break-glass 不变量的第三条路径无守卫:撤掉最后一个管理员的「身份」(sys_member 降级 / 删 admin_full_access 授权)同样锁死环境 #5978 的分诊同形 —— cloud ADR-0024 D5.2「环境永远至少留一个能登录的管理员」是已被接受的不变量,本条是它的第四条路径未被兑现,restore-invariant 形状,按分诊规则自动入队,不占维护者收件箱。正文两条方向的差别是实现覆盖面(多守一张表 vs 收紧引导期判据),不是产品语义拍板。⚠️ 但把一句话记在单上而不是记在记忆里:方向二会改变 fresh-install 的行为(admins.size === 0今天正是空环境的合法放行口),若识别期发现「确实处于引导期」的可判定条件收不干净、必须改变全新环境的可写性,那一刻才是维护者问题,由车道 PM 停手升级,不自行收窄。两条方向不互斥,判据一律 fail-closed(同该文件## Fail-closed段的既定姿态)。 - 顺带记一条严重性事实(供车道排期,不构成定级):本条不只锁死环境,还连带解除 plugin-auth: break-glass 守卫 —— SCIM/ban 不得停用最后一个管理员(ADR-0024 D5.2,cloud#621 转入) #5892 / 最后一个管理员的「删除」路径无守卫:目标不持本地密码时,SCIM/admin remove 可删掉环境最后一个管理员 #5941 / break-glass 不变量的第三条路径无守卫:撤掉最后一个管理员的「身份」(sys_member 降级 / 删 admin_full_access 授权)同样锁死环境 #5978 三条路径的守卫 —— 删掉
sys_permission_set那行后admins.size === 0被读成「引导期,无可保护」而对所有写放行。所以它在同族四条路径里是唯一一条会让既有守卫整体失效的。 - 查重(三仓 issue + PR 各搜一遍:
admin_full_access/last-admin-guard/sys_permission_set):plugin-auth: break-glass 守卫 —— SCIM/ban 不得停用最后一个管理员(ADR-0024 D5.2,cloud#621 转入) #5892(closed)、最后一个管理员的「删除」路径无守卫:目标不持本地密码时,SCIM/admin remove 可删掉环境最后一个管理员 #5941 / PR feat(plugin-auth): break-glass 守卫的 delete 半边 —— 最后一个管理员也删不掉 (#5941) #5993(closed,8dcf607)、break-glass 不变量的第三条路径无守卫:撤掉最后一个管理员的「身份」(sys_member 降级 / 删 admin_full_access 授权)同样锁死环境 #5978(open,在飞)三条既有路径均未覆盖sys_permission_set的写入面;三仓无重复单,不收敛,cross-link 即可。 - 过时前提检查(
git fetch origin main后逐行核9e3709a):resolveAdminUserIds的两半枚举在:288处仍以where: { name: ADMIN_FULL_ACCESS }取权限集行,引导期豁免仍在:396;该文件在 main 上的最近改动是8dcf607(最后一个管理员的「删除」路径无守卫:目标不持本地密码时,SCIM/admin remove 可删掉环境最后一个管理员 #5941 / PR feat(plugin-auth): break-glass 守卫的 delete 半边 —— 最后一个管理员也删不掉 (#5941) #5993),未触及本单指出的两处。前提成立。
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- 落点锚定:修复落在
解锁 + 认领(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。- 分支:
claude/issue-6084-break-glass-fourth-path;worktree:../objectstack-issue-6084;域:domain:identity - 文件面:
last-admin-guard.ts+ 其测试 + changeset(钩子注册若需触auth-plugin.ts沿 fix(plugin-auth): break-glass 补上第三条路径 —— 撤销管理员「身份」的写(sys_member 降级/删行、admin_full_access 授权删/改)同样被拒 (#5978) #6086 同形)。与在飞 PR fix(plugin-auth): 短信日配额拒发时 OTP / 邀请短信回 429,不再是 500 + 空响应体 (#6039) #6092(auth-manager.ts)零交集。 - 方向裁定(两条不互斥,都做;留否决窗):① 同形状扩到第四张表 ——
sys_permission_set的beforeUpdate(payload 触及name)/beforeDelete,复用 standing 机制;② 收紧引导期豁免 —— 把「admins.size === 0⇒ 放行」分辨为「真引导期(环境里无任何管理员证据)」与「刚被清空(授权行仍在、权限集行不见了)」,后者 fail-closed。②的红线照分诊原话:若「确实处于引导期」的可判定条件收不干净、必须改变全新环境的可写性 ⇒ 停手 needs_decision,不自行收窄 fresh-install 行为。
Generated by Claude Code
- 分支:
- added a commit that references this issue
on Aug 7, 2026 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
- ①(第四张表):
- added a commit that references this issue
on Aug 17, 2026
发现于 #5978(第三条路径)的实现过程,范围外,未在该 PR 修。
现状
packages/plugins/plugin-auth/src/last-admin-guard.ts的resolveAdminUserIds分两半枚举管理员,platform admin 那一半的第一步是:即「谁是 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不在内。所以第四条写法仍然绕开全部守卫:sys_permission_set行;name改成别的值。两者事后
adminSetIds为空 ⇒ 所有 platform admin 的授权行还在、sys_user行原封不动、sys_member行原封不动,但没有任何人被枚举为 platform admin。为什么比看上去严重
守卫有一条引导期豁免(合理且必要):
所以在一个「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,然后当前 resolves(无守卫);此后
ban(engine, 'usr_platform')也 resolves。可能的方向(未决)
sys_permission_set的beforeUpdate(payload 触及name)/beforeDelete,复用 break-glass 不变量的第三条路径无守卫:撤掉最后一个管理员的「身份」(sys_member 降级 / 删 admin_full_access 授权)同样锁死环境 #5978 的enforceStanding与applyPending—— 机制已经在位,PendingStandingWrite只需多认一张表。admins.size === 0⇒ 放行」这条引导期豁免收紧成「确实处于引导期」的可判定条件(例如环境里根本没有admin_full_access授权行 vs 有授权行但权限集不见了),这样第四条路径至少不会连带解除其余守卫。两者不互斥。判据同样要 fail-closed。是否值得单独守一张只在部署期写一次的表,由 PM/维护者裁。
参考
packages/plugins/plugin-auth/src/last-admin-guard.ts头注释「Scope in the other direction」段已把这一条明确标为不在 break-glass 不变量的第三条路径无守卫:撤掉最后一个管理员的「身份」(sys_member 降级 / 删 admin_full_access 授权)同样锁死环境 #5978 范围内,本单是那句话指向的登记Blocked-by: #5978
Generated by Claude Code