Skip to content

删除记录时「引用清理」的写入半边仍以操作人身份执行:引用表非空时删除依然 403(#12166 裁决刻意划定的边界,报一次让维护者知情) #12597

Description

@os-warren

一句话

#12166 的裁决(2026-08-26,选项 A)把删除前的引用检查切成了系统身份,约束 1 明确限定「删除路径的其他任何部分身份不变」。因此引用清理的写入半边 —— set_nullUPDATEcascade 的子记录 DELETE —— 仍以操作人身份执行。结果:原报告里那批「有删除权、对引用表无任何权限」的角色,只有在引用表为空时删除才通得过;引用表真的有引用行时,依然 403,只是报错从 find 变成了 update

不是 #12166 的实现缺陷,也不是要推翻裁决 —— 它正是约束 1 划定的边界。开这张卡只是因为该边界的实际影响没有被测量过,而它决定了原报告那 17 组「角色×对象」里到底有多少被真正修好。

证据(PR #12596 里已 pin 成断言)

packages/plugins/plugin-security/src/delete-reference-cleanup-system-identity.test.ts

it('the elevation is the CHECK only — a non-empty referencing table still needs
    the caller\'s own write authority (constraint 1)', ...)

  expect(err.code).toBe('PERMISSION_DENIED');
  expect(err.details?.object).toBe('os_ehr_andon_record');
  expect(err.details?.operation).toBe('update');   // ← 不再是 'find'

夹具与原报告同形:对象 A 被 B 的可空 lookup 引用(解析行为 = set_null),角色对 A 有完整删除权、对 B 无任何授权。B 为空 ⇒ 删除成功(#12166 修好的部分);B 有一行 ⇒ 仍然 403。

为什么值得单独判一次

  • 原报告的 17 组配置里,引用表非空的那些仍然按不动删除按钮,症状与修复前对用户完全一致(同样是一句通用「您没有执行此操作的权限」);从部署方视角看,问题只解决了一部分,而这一点在 删除记录时「引用清理」用操作人身份查询引用表,读权不足即整体 403(应以系统身份执行) #12166 的验收里看不出来。
  • 裁决为引用检查给出的论证 —— 引用完整性动作是引擎责任、主流平台一律以系统身份执行(RDBMS FK 的 SET NULLCASCADE、Salesforce 的 lookup 清空与级联删除均记载为绕过 sharing)—— 在文义上同样覆盖写入半边:FK 基线里真正以引擎身份执行的正是那个 SET NULLDELETE。裁决把范围钉在查询上是刻意的(卡片正文与决策分析通篇只说「查询」),但据我所读,那个范围收窄没有单独论证过写入半边应当保留操作人身份 —— 它更像是照着 issue 的措辞划的,而不是对写入半边下的判断。
  • 反方向也确实存在:cascade 的子删除以系统身份执行意味着「有 A 的删除权 ⇒ 可以删掉自己无权删除的 B 行」,这是一个远比读权外溢更大的权限语义变更,绝不该顺手带过。set_null(只清 FK 列)与 cascade(删整行)在这个维度上不对称,很可能需要分别裁。

建议处置

维护者判一次,三个方向:

  • A. 维持现状(裁决的字面范围):写入半边保持操作人身份。代价:原报告的部署仍需为每个可删角色补齐引用表的权限,「删除权 = 删除权 + 引用表权限」的隐藏耦合只是从读权变成了写权,在权限界面上同样不可见。
  • B. set_null 也切系统身份,cascade 不切:清空 FK 列是纯粹的完整性维护(且已有 属主守卫与级联 set_null 冲突:非特权删除 sys_user 时 owner_id 级联置空被 #3004 守卫拦截(级联中途失败) #3023__referentialFieldClear 标记把它标为引擎内部写入),而删掉整行是数据销毁,保留操作人授权。这一档最贴近 FK 基线。
  • C. 两者都切:完全对齐 Salesforce/Dataverse 的级联语义。权限面变更最大,需要独立论证。

⚠️ 无论哪个方向都是权限边界语义变更,条款②适用,应走 needs-user-decision 而不是直接派发。

出处


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions