Skip to content

Commit 1d73d45

Browse files
os-trumpclaude
andauthored
fix(plugin-security): stage RLS membership on the write check path so a membership-keyed check resolves on a bare insert (#16607) (#16722)
* wip: stage RLS membership on the write check path (#16607) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37 * test(plugin-security): count only governed-object reads in the #16607 pins Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37 * chore(gates): pin the #16607 harness's engine doubles in the engine-double ledger Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6b7d709 commit 1d73d45

4 files changed

Lines changed: 616 additions & 0 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@objectstack/plugin-security": patch
3+
---
4+
5+
An RLS `check` clause that reads a membership-resolver key now resolves on a bare insert.
6+
7+
An app that registers an `IRlsMembershipResolver` (ADR-0105 D11) and authors `using` + `check` twins reading its key — `record.employer_org in current_user.employer_org_ids` — saw reads resolve the key and every bare insert refused with `PERMISSION_DENIED` ("would violate a row-level CHECK"), whether or not the payload carried the value the policy wanted. The membership sets were staged onto the request context only inside the read-filter computation; `computeWriteCheckFilter` compiled the `check` clause against a context in which the key had never been staged, so the variable was unresolved, the policy dropped, and the write failed closed. The two write shapes that passed — a by-id update and an insert of a `controlled_by_parent` child — passed only because an earlier read on the same context (the pre-image, the master read) happened to stage it first.
8+
9+
The write path now stages the resolver's sets itself, immediately before the `check` clause compiles, so a `check` resolves exactly the variables its `using` twin resolves regardless of whether the request read first. Staging is memoized per request context, so the read-first shapes still consult the resolver once. Nothing is relaxed: with no resolver registered, a resolver that throws, or a key the resolver does not publish, the policy still drops out and the write is still refused.

0 commit comments

Comments
 (0)