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
RLS enforcement: the write check (packages/formula matches-filter) admits a cross-class field-to-field comparison that driver-sql's read refuses — one classification, one answer per policy (the engine half of #20347) #20355
Path: permissions that actually hold | 缺项 (no item enforces an RLS policy that compares fields of two classes) | P2
Filed and graded by the triage seat (objectstack-wide, seat post #6015, session_01W89enF2dYV7K4N2Fbfj33f), splitting #20347 on the #15661 / #20336 two-lane precedent. ⛔ Not a claim.
The defect (measured on #20347, through the real plugin-security + ObjectQL + driver-sql)
For a policy whose predicate is record.status != record.amount (text vs number) or record.status != record.photo (text vs image):
the read (using on find) answers INVALID_FILTER / 400, because driver-sql's crossFieldComparisonClass (sql-driver.ts about :2714) refuses it;
the write check (check on insert) is admitted and the row is stored, because the in-process evaluator (packages/formulamatches-filter) has no class rule.
That is one policy with two enforcement answers, the write side the permissive one.
Why it is its own card
#20347 keeps the contract and the authoring door (domain:spec): the comparison classification, exported once from @objectstack/spec/data, and the validateRlsPredicateEnforceability refusal in packages/lint. This card is the runtime half. Stacks and policies that never pass os validate (a Studio save, an API write) still reach enforcement, so the two evaluators must agree on their own.
The write check refuses a cross-class comparison exactly as the read does: the same INVALID_FILTER envelope, naming the policy and fields. It is never admitted.
Pin it on memory, SQLite and PostgreSQL: the text vs number and text vs image predicates are refused on read and write, and a same-class comparison is admitted on both (the control). Measure the formula-field cell (record.status != record.is_open) and include it.
Blocked-by: #20347
Path: permissions that actually hold | 缺项 (no item enforces an RLS policy that compares fields of two classes) | P2
Filed and graded by the triage seat (objectstack-wide, seat post #6015,
session_01W89enF2dYV7K4N2Fbfj33f), splitting #20347 on the #15661 / #20336 two-lane precedent. ⛔ Not a claim.Grade:
bug·security·priority:p2·domain:engine·area:access·pm:blocked.The defect (measured on #20347, through the real
plugin-security+ ObjectQL +driver-sql)For a policy whose predicate is
record.status != record.amount(text vs number) orrecord.status != record.photo(text vs image):usingonfind) answersINVALID_FILTER/ 400, becausedriver-sql'scrossFieldComparisonClass(sql-driver.tsabout :2714) refuses it;checkoninsert) is admitted and the row is stored, because the in-process evaluator (packages/formulamatches-filter) has no class rule.That is one policy with two enforcement answers, the write side the permissive one.
Why it is its own card
#20347 keeps the contract and the authoring door (
domain:spec): the comparison classification, exported once from@objectstack/spec/data, and thevalidateRlsPredicateEnforceabilityrefusal inpackages/lint. This card is the runtime half. Stacks and policies that never passos validate(a Studio save, an API write) still reach enforcement, so the two evaluators must agree on their own.Execution notes
driver-sqland thematches-filterwrite-check evaluator read [finding] An RLS predicate comparing two fields of different comparison classes (text vs number, text vs image) passes os validate; on driver-sql the using read answers 400 while the check insert is admitted and stored #20347's exported classification. ⛔ No second copy:crossFieldComparisonClassmoves to, or delegates to, the shared source.INVALID_FILTERenvelope, naming the policy and fields. It is never admitted.record.status != record.is_open) and include it.Clause-②: no (narrowing enforcement to the read's answer), BREAKINGminorif any stored policy trips it. The [finding]$newith an array comparand splits across backends: driver-sql and driver-memory refuse (400), driver-mongodb answers, formula matches every row — and both shared faces pass it #19886 2f census found 0.