Found while implementing #10091 (the sys_attachmentbeforeUpdate authorization guard). Filed rather than fixed: that card's surface is attachment-access-hooks.ts only, and this gap lives in packages/plugins/plugin-audit/src/audit-writers.ts.
What is there and what is not
Both capability gates register on insert only:
enforceFilesCapability — audit-writers.ts (engine.registerHook('beforeInsert', …, { object: 'sys_attachment' }), ~:1617): refuses a sys_attachment insert whose parent_object does not declare enable: { files: true } (403 FILES_DISABLED, fail-closed, opt-in means explicit).enforceFeedsCapability — same file: the enable.feeds twin on sys_comment inserts (403 FEEDS_DISABLED).
Neither fires on update. Both objects' access kits authorize a parent re-point on update — comment-access-hooks.ts has since #4630 (the new thread_id's parent must be readable), and attachment-access-hooks.ts does once #10091 lands (the new parent_object/parent_id must be editable) — but the re-point checks are access checks, not capability checks. So a caller with edit on a record whose object declares enable.files: false (spec default) cannot create an attachment on it, yet can move an existing attachment onto it via update; likewise a comment can be re-pointed into a thread on a feeds-disabled object.
On today's origin/main the update verb on sys_attachment is entirely ungated, so the dodge is trivially reachable; after #10091 it narrows to callers who pass the row + re-point access rules — the capability half is still never asked.
Why it looks like a defect
The gates' own doc comment says the flag answers "does this object allow comments/files at all" — a property of the target object, not of the verb that got the row there. object.zod.ts's enable.files description says the flag "permits attachments targeting this object; otherwise creation is rejected" — a re-point makes the row target the object without a creation.
Suggested shape
Register the same two enforcement functions on beforeUpdate for their objects, reading the effective new parent (payload value, falling back per row) — or extend the existing insert handlers' registration, since both already read only input.data. For sys_comment the parent object comes out of the new thread_id; for sys_attachment it is data.parent_object (only when present — an unchanged parent needs no re-check).
Backlinks: #10091 (where found; its re-point access rule is the neighbouring half), #2707/#2727 (the capability gates' own cards), #4630 (the comment kit's re-point rule).
Generated by Claude Code
Found while implementing #10091 (the
sys_attachmentbeforeUpdateauthorization guard). Filed rather than fixed: that card's surface isattachment-access-hooks.tsonly, and this gap lives inpackages/plugins/plugin-audit/src/audit-writers.ts.What is there and what is not
Both capability gates register on insert only:
enforceFilesCapability—audit-writers.ts(engine.registerHook('beforeInsert', …, { object: 'sys_attachment' }), ~:1617): refuses asys_attachmentinsert whoseparent_objectdoes not declareenable: { files: true }(403FILES_DISABLED, fail-closed, opt-in means explicit).enforceFeedsCapability— same file: theenable.feedstwin onsys_commentinserts (403FEEDS_DISABLED).Neither fires on update. Both objects' access kits authorize a parent re-point on update —
comment-access-hooks.tshas since #4630 (the newthread_id's parent must be readable), andattachment-access-hooks.tsdoes once #10091 lands (the newparent_object/parent_idmust be editable) — but the re-point checks are access checks, not capability checks. So a caller with edit on a record whose object declaresenable.files: false(spec default) cannot create an attachment on it, yet can move an existing attachment onto it via update; likewise a comment can be re-pointed into a thread on a feeds-disabled object.On today's
origin/mainthe update verb onsys_attachmentis entirely ungated, so the dodge is trivially reachable; after #10091 it narrows to callers who pass the row + re-point access rules — the capability half is still never asked.Why it looks like a defect
The gates' own doc comment says the flag answers "does this object allow comments/files at all" — a property of the target object, not of the verb that got the row there.
object.zod.ts'senable.filesdescription says the flag "permits attachments targeting this object; otherwise creation is rejected" — a re-point makes the row target the object without a creation.Suggested shape
Register the same two enforcement functions on
beforeUpdatefor their objects, reading the effective new parent (payload value, falling back per row) — or extend the existing insert handlers' registration, since both already read onlyinput.data. Forsys_commentthe parent object comes out of the newthread_id; forsys_attachmentit isdata.parent_object(only when present — an unchanged parent needs no re-check).Backlinks: #10091 (where found; its re-point access rule is the neighbouring half), #2707/#2727 (the capability gates' own cards), #4630 (the comment kit's re-point rule).
Generated by Claude Code