Filed unassigned, unlabeled for triage to grade and route — a concrete defect measured while implementing #7929's ruling B (driver-sql stops echoing $field operands), deliberately NOT fixed in that PR because it is outside what B rules on.
What B closed, and what it did not
B's ruling is operand-shaped: "driver-sql's INVALID_FILTER refusal stops echoing $field operands". Implemented, that covers the three cross-field builders plus five operators whose own comparand-shape gates rendered a reference into their message. It does not touch the rest of the INVALID_FILTER family, and every one of those still names the target field of the refused constraint.
That matters for exactly the reason #7929 exists. The predicate a driver refuses is frequently not the caller's: the security middleware ANDs an administrator's compiled CEL rule into opCtx.ast.where (plugin-security/src/security-plugin.ts, the $and merge), and the driver cannot tell the two subtrees apart. On such a predicate the target column is as admin-authored as the referent — which is the argument B accepted for withholding both operands of a cross-field comparison. The same argument applies one step out, to every refusal that names a column.
Measured
Real SqlDriver (better-sqlite3, :memory:), one object declaring secret_policy_col as multiple: true, driven through driver.find. Each of these is a predicate an RLS rule can plausibly produce, and each answers INVALID_FILTER / 400 naming the column:
REFUSED | JSON column + $in code=INVALID_FILTER names target? true
Operator "$in" on field "secret_policy_col" WAS NOT APPLIED: "secret_policy_col" is a
multi-value (or otherwise JSON-valued) field, stored by this driver as a JSON TEXT column …
REFUSED | empty field constraint code=INVALID_FILTER names target? true
Field constraint at filter.secret_policy_col carries zero operators
({ "secret_policy_col": {} }). …
REFUSED | unbindable comparand code=INVALID_FILTER names target? true
Operator "$gt" on field "secret_policy_col" requires a single comparable value, but received
an object ({"a":1}), which cannot be bound as a SQL parameter. …
The first is the most reachable of the three: a CEL permission rule over a multi-select field lowers to a membership test on a JSON-stored column, and #7398's gate refuses it — correctly — while naming the column the administrator wrote.
One residual inside the cross-field family itself, for completeness: a MALFORMED reference ({ "$gt": { "$field": 42 } } — not a FieldReferenceSchema, so the cross-field arm does not recognise it) falls to the generic unbindable-comparand refusal, which prints the target column and the comparand's JSON. No column name is disclosed by the comparand there, but the target is.
Explicitly NOT claimed
Refs
Blocked-by: #8220
Filed unassigned, unlabeled for triage to grade and route — a concrete defect measured while implementing #7929's ruling B (
driver-sqlstops echoing$fieldoperands), deliberately NOT fixed in that PR because it is outside what B rules on.What B closed, and what it did not
B's ruling is operand-shaped: "
driver-sql'sINVALID_FILTERrefusal stops echoing$fieldoperands". Implemented, that covers the three cross-field builders plus five operators whose own comparand-shape gates rendered a reference into their message. It does not touch the rest of theINVALID_FILTERfamily, and every one of those still names the target field of the refused constraint.That matters for exactly the reason #7929 exists. The predicate a driver refuses is frequently not the caller's: the security middleware ANDs an administrator's compiled CEL rule into
opCtx.ast.where(plugin-security/src/security-plugin.ts, the$andmerge), and the driver cannot tell the two subtrees apart. On such a predicate the target column is as admin-authored as the referent — which is the argument B accepted for withholding both operands of a cross-field comparison. The same argument applies one step out, to every refusal that names a column.Measured
Real
SqlDriver(better-sqlite3,:memory:), one object declaringsecret_policy_colasmultiple: true, driven throughdriver.find. Each of these is a predicate an RLS rule can plausibly produce, and each answersINVALID_FILTER/ 400 naming the column:The first is the most reachable of the three: a CEL permission rule over a multi-select field lowers to a membership test on a JSON-stored column, and #7398's gate refuses it — correctly — while naming the column the administrator wrote.
One residual inside the cross-field family itself, for completeness: a MALFORMED reference (
{ "$gt": { "$field": 42 } }— not aFieldReferenceSchema, so the cross-field arm does not recognise it) falls to the generic unbindable-comparand refusal, which prints the target column and the comparand's JSON. No column name is disclosed by the comparand there, but the target is.Explicitly NOT claimed
compileCelToFilterroutinely emits — so the reachable set is "an administrator wrote a rule the driver cannot compile for a reason other than cross-field".INVALID_FILTERwould cost every author of every malformed filter their diagnostic (a much wider version of the cost B already accepted), while the provenance mark finding: after the #7598 Q1=B ruling, a read scope with a driver-refused field reference answers 400 from the driver — which cuts across #5367's attribution argument on that one path #7929's follow-up card (A) declares would let the driver withhold only for policy-authored subtrees — and would close this class as a side effect, without the blanket cost. That makes this card look like evidence for A's scope rather than independent work, which is triage's call and not the dev seat's.Refs
$fieldoperands at the driver; the follow-up card A is the provenance mark)$fieldfilter injected by the security middleware discloses the same policy column on the ordinary CRUD path, and predates #7598 #7988 (the CRUD merge boundary, covered by B's PR)multiple: true(JSON array) column silently answers wrong —$in/$eqalways zero rows,$ninreturns the rows it was asked to EXCLUDE #7398 (the JSON-column operator gate whose message is the most reachable case above)Blocked-by: #8220