观察类发现(finding),来自 #5892 / PR #5939 的实现过程。今天没有用户会撞上 :两种拼写在所有小写取值上答案一致,而 UI 与 better-auth 写入的都是小写。记录下来是因为它是一条安全路径上的静默分歧。
两种拼写
等级尺 (唯一那把,packages/plugins/plugin-auth/src/invitation-role-cap.ts):parseOrgRoles() 会 .trim().toLowerCase(),orgRoleGrade() 据此评级。PR feat(plugin-auth): break-glass 守卫 —— ban 不得停用最后一个管理员(ADR-0024 D5.2) #5939 新导出的 isOrgAdminGrade() 就是它,break-glass ban 守卫用它数管理员。
手抄版 (packages/plugins/plugin-auth/src/auth-manager.ts:3625-3634,isOrgOrPlatformAdmin):
raw.split(',').map((s) => s.trim()).some((r) => r === 'owner' || r === 'admin')
不转小写。这段是 /sso/register 管理员门禁的判据(ADR-0024,fail-closed)。
分歧
sys_member.role 若存进 Owner / ADMIN(导入、外部写入、手工 SQL 都可能),同一行会被:
break-glass ban 守卫算作管理员 (于是可能允许 ban 掉另一个真管理员 —— 它以为还剩一个);
/sso/register 门禁算作非管理员 (于是拒绝一个本该允许的注册)。
两个方向的错都不响 —— 没有任何一处会报「这两处不一致」。
为什么现在只是观察
sys_member.role 的可选值来自 BUILTIN_MEMBERSHIP_ROLE_OPTIONS(ADR-0108 的封闭词表),值全为小写;better-auth organization 插件写入的也是小写。所以要造出分歧,得有一条绕过表单的写入。没有查到这样的生产路径。
可能的收口
把 isOrgOrPlatformAdmin 里那 6 行换成 isOrgAdminGrade(m.role)(语义相同,额外获得大小写与数组拼写的处理),这样「哪种 membership 算管理员」在 plugin-auth 内只剩一个答案。#5939 没有顺手做:那是另一条安全路径上的方法,不属于 #5892 的范围。
顺带记录:platform_admin 的推导目前有三处独立实现 —— packages/core/src/security/resolve-authz-context.ts(权威)、auth-manager.ts 的 customSession 与 isOrgOrPlatformAdmin、以及 #5939 的守卫(反方向枚举,resolveAuthzContext 按 user 查,回答不了「谁是管理员」这个集合问题)。四处今天一致,但和上面同属一类风险。
参考
Generated by Claude Code
观察类发现(
finding),来自 #5892 / PR #5939 的实现过程。今天没有用户会撞上:两种拼写在所有小写取值上答案一致,而 UI 与 better-auth 写入的都是小写。记录下来是因为它是一条安全路径上的静默分歧。两种拼写
等级尺(唯一那把,
packages/plugins/plugin-auth/src/invitation-role-cap.ts):parseOrgRoles()会.trim().toLowerCase(),orgRoleGrade()据此评级。PR feat(plugin-auth): break-glass 守卫 —— ban 不得停用最后一个管理员(ADR-0024 D5.2) #5939 新导出的isOrgAdminGrade()就是它,break-glass ban 守卫用它数管理员。手抄版(
packages/plugins/plugin-auth/src/auth-manager.ts:3625-3634,isOrgOrPlatformAdmin):不转小写。这段是
/sso/register管理员门禁的判据(ADR-0024,fail-closed)。分歧
sys_member.role若存进Owner/ADMIN(导入、外部写入、手工 SQL 都可能),同一行会被:/sso/register门禁算作非管理员(于是拒绝一个本该允许的注册)。两个方向的错都不响 —— 没有任何一处会报「这两处不一致」。
为什么现在只是观察
sys_member.role的可选值来自BUILTIN_MEMBERSHIP_ROLE_OPTIONS(ADR-0108 的封闭词表),值全为小写;better-auth organization 插件写入的也是小写。所以要造出分歧,得有一条绕过表单的写入。没有查到这样的生产路径。可能的收口
把
isOrgOrPlatformAdmin里那 6 行换成isOrgAdminGrade(m.role)(语义相同,额外获得大小写与数组拼写的处理),这样「哪种 membership 算管理员」在 plugin-auth 内只剩一个答案。#5939 没有顺手做:那是另一条安全路径上的方法,不属于 #5892 的范围。顺带记录:
platform_admin的推导目前有三处独立实现 ——packages/core/src/security/resolve-authz-context.ts(权威)、auth-manager.ts的customSession与isOrgOrPlatformAdmin、以及 #5939 的守卫(反方向枚举,resolveAuthzContext按 user 查,回答不了「谁是管理员」这个集合问题)。四处今天一致,但和上面同属一类风险。参考
isOrgAdminGrade的导出与第二个消费者)Generated by Claude Code