Skip to content

finding(auth): reconcileMembership 与 backfillMemberships 对「词表外的 policy」判断相反 —— 一个 fail-open,一个 fail-safe #5205

Description

@os-zhuang

观察类 finding,来自 #5152 的实现。默认部署撞不到#5152 之后两个调用点都走 AuthManager.getMembershipPolicy(),返回类型是 MembershipPolicy,词表外的值进不来。留档是因为这两个函数是 @objectstack/plugin-auth 的公开导出(index.tsexport * from './reconcile-membership.js'),JS 侧调用方或 cloud 宿主可以绕过类型直接传。

事实(reconcile-membership.ts

同一个策略字段,两个函数用相反的谓词:

reconcileMembership:   if (deps.policy === 'invite-only') return { outcome: 'policy-skip' };
backfillMemberships:   if (deps.policy !== 'auto')        return { ...summary, reason: 'policy' };

传入 'inviteOnly'(或任何拼错的值)时:

函数 行为
reconcileMembership(注册路径,逐个绑定) 落到 auto 分支,照常自动绑定 —— fail-open
backfillMemberships(回填路径,批量绑定) 判为非 auto不绑 —— fail-safe

两者对同一个输入给出相反的姿态。真正危险的方向是前者:调用方以为自己关掉了自动绑定,实际上没有。

为什么现在只是 finding

可能的形状

  1. reconcileMembership 改成与回填同向的 fail-safe:if (deps.policy !== 'auto') return { outcome: 'policy-skip' };。一行,语义收紧,且与「墙的默认姿态是有墙」一致。风险:policy-skip 这个 outcome 名对「值非法」这个原因是撒谎,排障时会误导;
  2. 两处都在入口用 isMembershipPolicy()membershipPolicy 无法作为平台设置配置,且注册路径与回填路径读的是两个来源 #5152 新增的类型守卫)显式判非法,给一个独立的 outcome/reason(如 'invalid-policy')并记 error。信息量最足,改动面也最大(ReconcileOutcome 是导出的联合类型,加成员对下游是 minor);
  3. 什么都不做——两个谓词的分歧记在这里,等哪天真有外部调用方再说。

倾向 2:与 #5152 在 settings 边界上采取的姿态(非法值响亮拒绝、不静默强转)一致,且不会让 outcome 名撒谎。但它改的是导出的联合类型,属于契约变更,需要维护者定夺。

Found-during: #5152

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions