Found while implementing #9719 (the sys_attachment sibling; PR #9797). Reported, not fixed — #9719's ruling commissions the sys_attachment delete restoration only, and generalizing was explicitly left to its own card.
The same defect, one package over, on two verbs
packages/plugins/plugin-audit/src/comment-access-hooks.ts guards sys_comment writes through resolveTargetRows (declared at #4630): when a multi-write carries no id and no where (where === undefined || null), it refuses outright — Refusing an unscoped multi-{update|delete} of comments — scope the write to the rows you mean — rather than authorizing the whole table by resolving zero rows.
That branch is unreachable through the wired engine, by exactly the #9719 mechanism, on both verbs:
Both callers are affected: the beforeUpdate handler and the beforeDelete handler resolve rows through this one function. The per-row authorization that DOES fire still gates each matched row individually, so this is the #9719 blast-radius shape, not a privilege escalation: an unscoped multi: true write silently touches every row the caller happens to be entitled to, where the declared behaviour is an outright refusal.
Unit coverage: comment-access-hooks.test.ts pins the refusal by direct handler calls with hand-rolled whole-operation contexts — the same false-green shape #9719's ruling ordered killed for the attachment suite (a unit suite constructing its own context cannot see the wired engine's dispatch shape).
Route notes (for triage, not a proposal)
Backlink: #9719, PR #9797 (mechanism and measured limbs), #4630 (the guard's own declaration).
Generated by Claude Code
Found while implementing #9719 (the sys_attachment sibling; PR #9797). Reported, not fixed — #9719's ruling commissions the
sys_attachmentdelete restoration only, and generalizing was explicitly left to its own card.The same defect, one package over, on two verbs
packages/plugins/plugin-audit/src/comment-access-hooks.tsguardssys_commentwrites throughresolveTargetRows(declared at#4630): when a multi-write carries no id and nowhere(where === undefined || null), it refuses outright —Refusing an unscoped multi-{update|delete} of comments — scope the write to the rows you mean— rather than authorizing the whole table by resolving zero rows.That branch is unreachable through the wired engine, by exactly the #9719 mechanism, on both verbs:
resolveTargetRowsopens withasIdList(ctx?.input?.id)and takes the by-id path wheneverinput.idis bound — and the per-row dispatch contract ([17.x] 批量写按行语义实现:hook 按行触发 + record-change trigger 按行绑定 previous/record(#4800/#4862 拍板 A) #5038/beforeUpdate hook 在 multi:true 批量更新上拿不到 ctx.previous —— sys_fetch_previous_update 依赖 input.id;引擎已为校验取 priorRows 却不喂 hook(17.0.0-rc.2) #5574) bindsinput.idon everybeforeUpdateandbeforeDeletedispatch of a predicate write.Both callers are affected: the
beforeUpdatehandler and thebeforeDeletehandler resolve rows through this one function. The per-row authorization that DOES fire still gates each matched row individually, so this is the #9719 blast-radius shape, not a privilege escalation: an unscopedmulti: truewrite silently touches every row the caller happens to be entitled to, where the declared behaviour is an outright refusal.Unit coverage:
comment-access-hooks.test.tspins the refusal by direct handler calls with hand-rolled whole-operation contexts — the same false-green shape #9719's ruling ordered killed for the attachment suite (a unit suite constructing its own context cannot see the wired engine's dispatch shape).Route notes (for triage, not a proposal)
beforeDeletehook withdispatchUnscopedMultiDelete: trueand the engine delivers the whole-operation context before any row is resolved, zero-match included.beforeDelete(extending it tobeforeUpdate's predicate path is a product-behaviour decision of its own, and the engine refuses the flag on other events precisely so this arrives as a decision rather than drift). An unscopedmulti: trueupdate is data corruption rather than data loss, but the declared refusal is the same.comment-access-hooks.test.tsshould be re-pointed at the wired path in the same PR, as The #4757 unscoped multi-delete refusal on sys_attachment never fires through ObjectQL.delete — per-row hook dispatch bypasses it #9719's ruling required for the attachment suite — no false green left behind.Backlink: #9719, PR #9797 (mechanism and measured limbs), #4630 (the guard's own declaration).
Generated by Claude Code