You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
security(engine): exempt the referential FK-clear write from the object-level CRUD check (#12941)
* feat(security): marker-scoped CRUD exemption for the referential FK clear
The engine's `set_null` reference cleanup already tags its UPDATE with the
server-derived `__referentialFieldClear` marker (#3023). The marker reached only
the ownership-anchor guard, so the cleanup write still had to pass the
object-level CRUD check on the referencing object: a role holding full delete
rights on A and no grant at all on B could delete an A only while B was empty.
plugin-security now exempts a marked UPDATE from the object-level CRUD grant
check, and from that check alone. Field-level security on the FK column, the RLS
`using` row scope and the RLS post-image `check` all stay enforced -- each pinned
-- and the `cascade` arm keeps the operator's own delete authority, untouched.
The write is not elevated: it stays the operator's identity, so audit
attribution (`user_id`/`actor`, `updated_by`) is unchanged.
Ruled by the maintainer on 2026-08-28 (#12597, second round, option B).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZbWd2jNV1FErXTPSS4Dry
* test(security): ablation prediction for the #12597 CRUD exemption
Committed BEFORE the mutation so the prediction cannot be written after the
reading. Reverting the exemption (restoring the bare
`if (permissionSets.length > 0)` at the object-level CRUD check) is predicted to
turn SIX of the fourteen tests in
`delete-reference-cleanup-system-identity.test.ts` RED:
1. `[#12597 - PIN INVERTED] a non-empty referencing table no longer needs ...`
2. `THE CONTRACT: full delete on A + NOTHING on B + a non-empty ...`
3. `GUARD 1 - field-level security on the FK column still refuses ...`
4. `GUARD 2 - the RLS \`using\` row scope ... still refuses ...`
5. `GUARD 2 CONTROL - the same policy admits the write when the row IS in scope`
6. `GUARD 3 - the RLS post-image \`check\` still refuses ...`
Guards 1-3 are predicted red DESPITE still refusing, because each asserts its
own gate's ADR-0112 envelope (`forbiddenFields`, `recordId` +
"row-level security", "row-level CHECK") and the reverted CRUD check answers
first with none of them. That is the whole reason those pins are written on
envelopes rather than on the bare fact of a refusal.
The other two new arms are predicted GREEN either way: the converse (no delete
right on the target) and the ordinary unmarked update, neither of which the
exemption is in a position to change.
Note on the ablation's mechanics: the mutated file is
`plugin-security/src/security-plugin.ts`, which this suite imports RELATIVELY
(`./security-plugin.js`), so vitest reads the SOURCE in this checkout and no
rebuild leg applies to it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZbWd2jNV1FErXTPSS4Dry
* chore(changeset): minor bump for the referential FK-clear CRUD exemption
MINOR by the maintainer's 2026-08-28 ruling: the change moves which deletes
succeed, an observable behavioural contract change on the delete path rather
than a patch-grade defect repair.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZbWd2jNV1FErXTPSS4Dry
* docs(security): point the exemption comment at the suite that exists
The comment cited `delete-reference-cleanup-crud-exemption.test.ts`, a filename
that resolves to nothing: the pins live in this package's
`delete-reference-cleanup-system-identity.test.ts`, in its `#12597` describe.
Caught in review. A citation that resolves to nothing is the defect class the
ADR-anchor gate exists to name, and it is worse in a comment explaining a
permission boundary, where the next reader is looking for the evidence.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZbWd2jNV1FErXTPSS4Dry
---------
Co-authored-by: Claude <noreply@anthropic.com>
0 commit comments