fix(security): 级联 set_null 豁免属主转移守卫 (#3023)#3048
Merged
Merged
Conversation
…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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 21 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This was referenced Jul 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
#3004(PR #3018)给
owner_id加了属主锚点守卫:非特权写入者携带owner_id(含置null的弃权)在无 transfer 授权时被拒。owner_id是指向sys_user的lookup,默认deleteBehavior: 'set_null'。删除一个sys_user时,cascadeDeleteRelations会对每个引用它的子记录执行update(child, { id, owner_id: null }, { context }),沿用删除者的上下文重新进入写中间件。守卫把这个owner_id = null当成用户发起的弃权而抛PermissionDeniedError—— 由于没有事务包裹,级联中途失败留下部分置空的子记录。触发面:删除者缺少子对象上的 transfer 授权(典型如成员清理一个public_read_write子对象,RLS 本会放行、却被守卫拦下)。修复
级联
set_null是引擎内部的引用完整性写入,是已授权父删除的强制后果,不是用户的属主变更——不应被属主转移守卫拦截。cascadeDeleteRelations):set_null写入改带一个服务端派生的上下文标记__referentialFieldClear(由引擎设置、绝不来自请求体——与既有的__expandRead同一信任模型)。普通用户写路径不受影响;标记无法从客户端伪造(执行上下文由运行时按会话解析,不透传请求字段),因此永远无法借它把真正的属主转移偷渡过守卫。
范围说明:本 PR 精确修复 #3004 守卫对级联的过度拦截,不改变级联的可见性范围(
find仍按调用者上下文发现子记录)。“引用完整性是否应覆盖删除者不可见的子记录(系统级 find)”是更大的独立设计问题,不在本 PR。验证
engine-cascade-delete.test.ts):证明级联set_null写入携带__referentialFieldClear,而普通用户 update 不带(对照);且级联确实把 FK 置空。security-plugin.test.ts):证明带标记的owner_id: null被成员放行,不带标记仍被拒(既有弃权用例)。关联
🤖 Generated with Claude Code
Generated by Claude Code