Skip to content

最后一个管理员的「删除」路径无守卫:目标不持本地密码时,SCIM/admin remove 可删掉环境最后一个管理员 #5941

Closed

Description

@baozhoutao

发现于 #5892 / PR #5939 的实现过程(范围外,未在该 PR 修)。

现状

#5892 只要求守住 ban(sys_user.banned = true),PR #5939 因此把不变量装在 sys_user 的 beforeUpdate 上。删除这条路径上的同一条不变量,今天由两段代码覆盖,而两段都不判断「管理员还剩几个」:

  1. packages/plugins/plugin-auth/src/auth-manager.ts:1239-1300 —— /delete-user、/admin/remove-user、/admin/ban-user 三条路径上的 break-glass 守卫。它判定的是最后一个本地 credential 账户持有人:
const targetCred = await adapter.findOne({ model: 'account', where: [
  { field: 'userId', value: targetId }, { field: 'providerId', value: 'credential' } ] });
if (targetCred) { /* 只有目标自己持有本地密码时才继续数 */ }

目标不持本地凭据(IdP 托管 / SCIM JIT provision 出来的用户)时,守卫整段跳过。

  1. identity-write-guard.ts(ADR-0092 D2)拒绝用户上下文的 sys_user delete,但按设计放行 isSystem —— better-auth adapter 自己的写入正是 system。

可达的锁死

一个 enforced SSO 环境,最后一个管理员由 IdP 托管(无本地密码),IdP 侧把他移出组 → SCIM DELETE /Users/{id}(或 /admin/remove-user)→ 两段守卫都不触发,用户行被删除。环境里可能还留着一个持密码的非管理员逃生账户:能登录,但不能管理任何东西 —— 与 ADR-0024 D5.2 想避免的状态是同一类。

PR #5939 的 ban 守卫不覆盖这条:它是 beforeUpdate,delete 走 beforeDelete。

可能的方向(未决)

  • 把「剩余 0 个未被 ban 的管理员则拒绝」的判定从 last-admin-ban-guard.ts 复用到 beforeDelete(同一份 resolveAdminUserIds,目标集换成被删行);
  • 或者把 auth-manager 那段 HTTP 守卫的判据从「最后一个 credential 持有人」扩成「最后一个管理员」—— 但那段是 fail-open 的,与不变量要求的 fail-closed 方向相反,合并前需要先决定方向。

两条不等价:HTTP 守卫只覆盖 better-auth 的三条端点,引擎钩子覆盖所有写入路径(#5939 的头注释解释了为什么选后者)。

参考


Generated by Claude Code

Activity

  1. claude commented on Aug 6, 2026

    @claude
    Contributor

    分诊:pm:queue + domain:identity。

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


    Generated by Claude Code

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

    @baozhoutao
    ContributorAuthor

    认领:PM 循环第 6 轮(domain:identity 车道)
    会话:session_01JwwiU9bjhwy2SWj13ho8uv
    分支:claude/issue-5941-last-admin-delete-guard
    Worktree:objectstack-issue-5941
    域:domain:identity
    文件面:packages/plugins/plugin-auth/**(last-admin-ban-guard.ts 扩展/复用 + 注册 + 测试)+ .changeset/*;auth-manager.ts 原则上只读(fail-open 段不动,分诊明令);⛔ 触 @better-auth/scim 模型面或 packages/objectql 即停

    PR #5939 已 MERGED(61dc08ead),本单从合并后 main 切分支复用其 resolveAdminUserIds。方向按分诊:引擎钩子 fail-closed(方向一);beforeDelete 拿不到判定行集(级联/批量)即 STOP 上报。#5942(等级尺两种拼写)同包待下轮串行,本单不顺手修它。


    Generated by Claude Code

  4. baozhoutao commented on Aug 6, 2026

    @baozhoutao
    ContributorAuthor

    复核 ACCEPT(PM,session_01JwwiU9bjhwy2SWj13ho8uv,第 6 轮):交付 PR #5993。

    CI 24 检查全绿,转 ready 入合并队列;落地后开第 7 轮(#5942,同包串行解除)。


    Generated by Claude Code

  5. added a commit that references this issue on Aug 12, 2026
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