Skip to content

fix(security): 级联 set_null 豁免属主转移守卫 (#3023)#3048

Merged
os-zhuang merged 1 commit into
mainfrom
claude/owner-id-security-fix-m36705
Jul 16, 2026
Merged

fix(security): 级联 set_null 豁免属主转移守卫 (#3023)#3048
os-zhuang merged 1 commit into
mainfrom
claude/owner-id-security-fix-m36705

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

背景

#3004(PR #3018)给 owner_id 加了属主锚点守卫:非特权写入者携带 owner_id(含置 null 的弃权)在无 transfer 授权时被拒。

owner_id 是指向 sys_userlookup,默认 deleteBehavior: 'set_null'。删除一个 sys_user 时,cascadeDeleteRelations 会对每个引用它的子记录执行 update(child, { id, owner_id: null }, { context }),沿用删除者的上下文重新进入写中间件。守卫把这个 owner_id = null 当成用户发起的弃权而抛 PermissionDeniedError —— 由于没有事务包裹,级联中途失败留下部分置空的子记录。触发面:删除者缺少子对象上的 transfer 授权(典型如成员清理一个 public_read_write 子对象,RLS 本会放行、却被守卫拦下)。

修复

级联 set_null 是引擎内部的引用完整性写入,是已授权父删除的强制后果,不是用户的属主变更——不应被属主转移守卫拦截。

  • engine(cascadeDeleteRelations):set_null 写入改带一个服务端派生的上下文标记 __referentialFieldClear(由引擎设置、绝不来自请求体——与既有的 __expandRead 同一信任模型)。
  • plugin-security(step 3.5 属主守卫):当该标记存在时跳过守卫。

普通用户写路径不受影响;标记无法从客户端伪造(执行上下文由运行时按会话解析,不透传请求字段),因此永远无法借它把真正的属主转移偷渡过守卫。

范围说明:本 PR 精确修复 #3004 守卫对级联的过度拦截,不改变级联的可见性范围(find 仍按调用者上下文发现子记录)。“引用完整性是否应覆盖删除者不可见的子记录(系统级 find)”是更大的独立设计问题,不在本 PR。

验证

  • engine(engine-cascade-delete.test.ts):证明级联 set_null 写入携带 __referentialFieldClear,而普通用户 update 不带(对照);且级联确实把 FK 置空。
  • plugin-security(security-plugin.test.ts):证明带标记的 owner_id: null 被成员放行,不带标记仍被拒(既有弃权用例)。
  • 回归:objectql 899、plugin-security 462 全绿;相关 dogfood(owner-anchor-and-bulk-writes、private OWD、controlled-by-parent、authz-conformance)全绿。

关联

🤖 Generated with Claude Code


Generated by Claude Code

…transfer guard (#3023)

owner_id is a lookup to sys_user with default deleteBehavior 'set_null', so
deleting a sys_user makes cascadeDeleteRelations null owner_id on every dependent.
That cascade write re-entered the write middleware under the deleter's context,
where the #3004 ownership-anchor guard read owner_id=null as a user disown and
denied it — aborting the cascade mid-way (no transaction → partial state) for any
deleter lacking the transfer grant on the child (e.g. a member clearing a
public_read_write child that RLS would otherwise allow).

The cascade FK clear is engine-mandated referential integrity consequent to an
already-authorized parent delete, not a user ownership change. cascadeDeleteRelations
now tags the set_null write with a server-derived __referentialFieldClear context
marker (set by the engine, never built from a request — same trust model as
__expandRead), and the ownership-anchor guard skips when it is present. Ordinary
user writes are unaffected; the marker can't be forged from client input, so it can
never slip a real ownership transfer past the guard.

Tests: engine-cascade-delete proves the set_null write carries the marker (and a
user update does not); plugin-security proves the guard honors it (a member's
owner_id=null with the marker is allowed, denied without). objectql 899 /
plugin-security 462 / related dogfood green.
@vercel

vercel Bot commented Jul 16, 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 16, 2026 11:31am

Request Review

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/objectql, @objectstack/plugin-security.

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

  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/objectql)
  • content/docs/data-modeling/formulas.mdx (via packages/objectql)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql)
  • content/docs/getting-started/cli.mdx (via @objectstack/plugin-security)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/objectql)
  • content/docs/kernel/services.mdx (via @objectstack/objectql)
  • content/docs/permissions/access-recipes.mdx (via packages/plugins/plugin-security)
  • content/docs/permissions/authentication.mdx (via @objectstack/objectql)
  • 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/objectql, @objectstack/plugin-security)
  • content/docs/plugins/packages.mdx (via @objectstack/objectql, @objectstack/plugin-security)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/objectql)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql)
  • content/docs/releases/implementation-status.mdx (via @objectstack/objectql, @objectstack/plugin-security)
  • content/docs/releases/v9.mdx (via @objectstack/objectql)
  • 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.

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/s tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

属主守卫与级联 set_null 冲突:非特权删除 sys_user 时 owner_id 级联置空被 #3004 守卫拦截(级联中途失败)

2 participants