Skip to content

「这个 membership 是不是管理员」有两种拼写,大小写敏感性不同:isOrgOrPlatformAdminrole='Owner' 答否,等级尺答是 #5942

Description

@baozhoutao

观察类发现(finding),来自 #5892 / PR #5939 的实现过程。今天没有用户会撞上:两种拼写在所有小写取值上答案一致,而 UI 与 better-auth 写入的都是小写。记录下来是因为它是一条安全路径上的静默分歧。

两种拼写

  1. 等级尺(唯一那把,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 守卫用它数管理员。

  2. 手抄版(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.tscustomSessionisOrgOrPlatformAdmin、以及 #5939 的守卫(反方向枚举,resolveAuthzContext 按 user 查,回答不了「谁是管理员」这个集合问题)。四处今天一致,但和上面同属一类风险。

参考


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