Skip to content

fix(plugin-security): 约束 self-delegation position anchor 防横向可见性越权(#830 follow-up) - #2945

Merged
os-zhuang merged 1 commit into
mainfrom
claude/authz-secfix-self-deleg-anchor
Jul 15, 2026
Merged

fix(plugin-security): 约束 self-delegation position anchor 防横向可见性越权(#830 follow-up)#2945
os-zhuang merged 1 commit into
mainfrom
claude/authz-secfix-self-deleg-anchor

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

漏洞(MEDIUM,横向可见性越权 / lateral escalation)

cloud#830(C1 position-anchor,已合并)把 sys_user_position.business_unit_id 从「reserved」变成可见性 load-bearing:它是 readScope 的深度锚定,unit/unit_and_below 的持有者可见的 owner 集合以该 BU 为根(unit_and_below 覆盖整棵子树)。

委派管理员的自助代理(D3 self-delegation)路径 assertSelfDelegationpackages/plugins/plugin-security/src/delegated-admin-gate.ts)此前对 business_unit_id 没有任何 subtree/来源约束。后果:

  • Alice 只需直接持有一个 delegatable: true、带 unit/unit_and_below readScope 的非 admin-scope position P(锚定在低层 BU)。
  • 她把 P 代理给同谋 Bob,并把 business_unit_id 设成任意高层 / 祖先 BU
  • Bob 遂获得该 BU(及 unit_and_below 整棵子树)成员记录的可见性——超出 Alice 自己的范围
  • 互相代理可双向获得。

resolver 的「锚定只收窄不放宽」断言在 anchor 是成员 BU 祖先时不成立——而 D3 路径正是唯一没堵住这条的地方。主攻击面(直接自写 sys_user_position、自我代理、委派管理员路径的 anchor 落自己 subtree)此前均已封死。

修法选择与理由

采用任务的首选方案(约束而非禁止),理由:产品语义上 D3 代理本就允许携带 anchor(例如把「华东销售经理」职务代理给某人、锚定在华东某子单元)——一刀切禁止 anchor 会砍掉合法用法。改为强制收窄不放宽

self-delegation 的 business_unit_id 必须落在委派人自己对该 position 的有效 anchor 之内。

「有效 anchor」= 委派人自己直接持有该 position 的行的 anchor 子树;若该直接持有行本身无 anchor,则回退到委派人成员 BU 的子树(无锚持有解析到本人成员 BU)。这与 D12 委派管理员路径「assignments 必须落自己 subtree」(assertAssignmentWrite,L392-402)同精神、复用同款 subtree 逻辑

  • Fail-closed:无法证明落在委派人范围内的 anchor(范围不可解析)一律拒。
  • 无 anchor 的代理行保持原行为:代理人解析到自己的成员 BU,不构成放宽。

实现:activeHoldings 现返回每个持有行的 businessUnitId;新增 resolveSubtreeById / delegatorAnchorSubtreeresolveSubtree 重构复用之,行为不变);assertSelfDelegation 新增步骤 4b anchor 收窄校验。

测试矩阵(实测全绿,401 passed)

场景 期望 结果
anchor = 委派人自己 anchor(east) 放行
anchor 落委派人子树内(east_sales,收窄) 放行
anchor = 祖先 BU(hq,放宽) (never widen)
anchor = 无关兄弟 BU(west) (outside your own effective anchor)
无 anchor 的 D3 行 原行为,放行
互相代理跨范围(双向) 两个方向都
委派人无锚持有 → 用成员 BU 兜底:成员 BU 及以下放行、其父拒 收窄成立
fail-closed:委派人无可解析范围 (cannot be validated)
经代理获得的持有行不能再作源做带锚再代理 (chains cut,原有 D3 校验先命中)
委派管理员路径(非 delegation write)不受影响 原行为 ✅(既有测试)
直接自写 sys_user_position 仍封禁 原行为 ✅(既有测试)

pnpm --filter @objectstack/plugin-security test → 18 files / 401 tests passed;tsc --noEmit 0 error;build 通过。

是否彻底堵住

load-bearing 的 unit_and_below 情形:Bob 的 owner 集合 ⊆ Alice 的 owner 集合,「锚定只收窄不放宽」不变量在 D3 路径重建。祖先 / 任意高层 / 无关 BU 的锚定被 fail-closed 拒绝,主向量关闭。

存疑点 / reviewer 注意

  1. unit(单层,非 _and_below)readScope 下的子孙 anchor:本修复用 subtree 包含作边界(与 D12 admin 路径一致)。若 P 的 readScope 是纯 unit,Alice 锚定 east 只看 east 一层,而允许 Bob 锚定 east 的子孙 east_sales 在严格意义上让 Bob 看到 Alice 看不到的一层(横向而非放宽)。这与 D12 委派管理员路径既有的同一边界取舍一致(该路径也用 subtree),且主向量(祖先/高层放宽)已完全关闭。若要对纯 unit 也精确收敛,需在 gate 里解析 position 所分发各 set 的 per-object readScope——依赖 cloud enterprise resolver 语义,超出本 follow-up 范围,建议单独 issue 跟踪。
  2. anchor 是 BU id(lookup)而非 name:故新增 resolveSubtreeById(既有 resolveSubtree 按 name 解析根后同样走子树遍历,已重构复用)。
  3. cloud resolver(security-enterprise/hierarchy/resolver.tsanchoredBusinessUnits)不在本仓库 checkout 内,anchor→owner 语义依 ADR-0090 Addendum 与任务描述推断;建议 reviewer 对照 cloud#830 确认「无 anchor = 解析到持有者成员 BU」这一回退语义无误(本修复的成员 BU 兜底依赖它)。

Reviewer checklist

  • 确认 anchor 收窄边界(subtree 包含)符合 cloud#830 的 anchor→owner 语义
  • 确认「无 anchor D3 = 原行为」是期望的产品语义(未收紧无锚代理)
  • 确认成员 BU 兜底(无锚直接持有)符合 resolver 的无锚回退
  • 确认存疑点 1(纯 unit 子孙 anchor)取舍可接受,或决定单开 issue
  • fail-closed 分支(范围不可解析→拒)无误伤合法路径

关联

  • 起因:cloud#830(C1 position-anchor,已合并)
  • 跟踪:framework#2920(本横向越权向量)

🤖 Generated with Claude Code

https://claude.ai/code/session_019QRUvVfpvSycAHMMF2xTxs

…follow-up)

cloud#830 (C1 position-anchor) 把 sys_user_position.business_unit_id 变成可见性
load-bearing(readScope 的深度锚定)。自助代理(D3)路径 assertSelfDelegation 对该
anchor 无任何 subtree/来源约束:持有一个 delegatable、非 admin-scope、锚定在低层 BU
的 position 的用户,可把它代理给同谋并把 business_unit_id 设成任意高层/祖先 BU,从而
泄露该 BU 整棵子树的成员记录可见性——超出委派人自身范围;互相代理可双向获得。

修复:self-delegation 的 business_unit_id 必须落在委派人自己对该 position 的有效
anchor 之内(自己直接持有行 anchor 的子树,或持有行无 anchor 时其成员 BU 的子树)——
与 D12 委派管理员「assignments 必须落自己 subtree」同精神。fail-closed:无法证明落在
委派人范围内的 anchor 一律拒。无 anchor 的代理行保持原行为(代理人解析到自己的成员
BU,非放宽)。「锚定只收窄不放宽」不变量在 D3 路径重新成立。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QRUvVfpvSycAHMMF2xTxs
@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 15, 2026 3:49am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/m labels Jul 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-security.

11 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/getting-started/cli.mdx (via @objectstack/plugin-security)
  • content/docs/permissions/access-recipes.mdx (via packages/plugins/plugin-security)
  • content/docs/permissions/authorization.mdx (via packages/plugins/plugin-security)
  • content/docs/permissions/explain.mdx (via @objectstack/plugin-security)
  • content/docs/permissions/permissions-matrix.mdx (via packages/plugins/plugin-security)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/plugin-security)
  • content/docs/plugins/index.mdx (via @objectstack/plugin-security)
  • content/docs/plugins/packages.mdx (via @objectstack/plugin-security)
  • content/docs/releases/implementation-status.mdx (via @objectstack/plugin-security)
  • content/docs/ui/audience-based-interfaces.mdx (via packages/plugins/plugin-security)
  • content/docs/ui/dashboards.mdx (via @objectstack/plugin-security)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review July 15, 2026 03:45
@os-zhuang
os-zhuang merged commit 698454e into main Jul 15, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/authz-secfix-self-deleg-anchor branch July 15, 2026 03:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants