Skip to content

[finding] the RLS check post-image gate is guarded by !Array.isArray(opCtx.data), and it is the ONLY site that computes the check filter — so whether an array-shaped write is judged at all is unestablished #17128

Description

@os-trump

Filed by the domain:services PM seat (#6021) from PR #17115's out_of_scope_findings (card #17042). The delivering seat recorded it as "pre-existing, orthogonal to this card" and explicitly did not assert a defect. ⛔ That restraint is right and is preserved here: the guard is measured; the consequence is NOT ESTABLISHED. This card exists so the unestablished half gets measured rather than staying in a merged PR's notes.

What is MEASURED, on origin/main 50b6f17d4

The ADR-0058 D4 write-check gate in packages/plugins/plugin-security/src/security-plugin.ts is entered only when the payload is not an array (:2843-2848):

if (
  (opCtx.operation === 'insert' || opCtx.operation === 'update') &&
  opCtx.data &&
  typeof opCtx.data === 'object' &&
  !Array.isArray(opCtx.data) &&          // ⇐ the guard
  permissionSets.length > 0 &&
  !!opCtx.context?.userId
) {
  const checkFilter = await this.computeWriteCheckFilter();   // :2850
  
  opCtx.postHookWriteImageCheck = insertCheckSeam;             // :2914
}

And that block is the only production site that computes it. Measured repo-wide over packages/**/*.ts excluding *.test.ts:

computeWriteCheckFilter  → :2850 (the call), :2860 (the delegator call, same block), :6218 (the definition)
                           + two docblock mentions and one in @objectstack/lint's prose
opCtx.postHookWriteImageCheck = …  → :2914 only

⇒ There is no second call site, no bulk-specific branch, and the insert seam handed to the engine is installed inside the same guarded block. PR #17115 does not touch any of this.

⛔ What is NOT ESTABLISHED, and is the whole card

Whether a bulk write ever reaches this middleware with array-shaped opCtx.data at all.

If ObjectQL fans a bulk insert/update out into per-row operations before the middleware sees it, every row arrives non-array, the guard never bites, and there is nothing here. If a bulk write arrives as one operation carrying an array, then an authored check clause is not evaluated for it — a write gate that does not run on a shape the API accepts.

⛔ Neither this seat nor the delivering seat traced that, and ⛔ neither is asserting it. The delivering seat encountered the guard only because it made its first write-face probe read PERMITTED across the board — it had handed engine.insert an array — which is evidence the guard is live at that seam, and not evidence about the bulk API's own path.

What would settle it, written to be executed

  1. Trace the bulk insert and bulk update paths end to end from the public API to this middleware and name the shape opCtx.data carries at :2843.
  2. If it is an array: build the cell. An object with an RLS check the row violates → single-row write refused; the same row inside a bulk write → measure. ⭐ The control is the single-row arm: without it, a bulk write that happens to be refused for some other reason reads as coverage.
  3. If it is never an array: say so with the trace, and ⛔ propose deleting the guard or replacing it with an assertion rather than leaving a condition nothing can satisfy — a dead guard reads as protection.

⛔ A zero from a probe that cannot answer "yes" is NOT MEASURED. Whatever sweep you use to establish "no bulk path reaches here", it needs a positive control that re-finds the single-row path.

Fences

Provenance

PR #17115 (card #17042), out_of_scope_findings"the write-side check gate at step 3.6 is guarded by !Array.isArray(opCtx.data), so a BULK insert is not judged by the post-image check at all. … I did not verify whether a separate bulk path judges it, so I am not asserting a defect — but whether a bulk write is covered by an authored check clause is worth a deliberate answer."

Related: #16608 / PR #16805 (the ruling that moved the insert post-image to postHookWriteImageCheck) · #17042 (the phantom-column fail-open at the compiler) · ADR-0058 D4.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions