Skip to content

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

Description

@objectstack-fleet

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) 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/formula matches-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.

Execution notes

  1. driver-sql and the matches-filter write-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: crossFieldComparisonClass moves to, or delegates to, the shared source.
  2. 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.
  3. 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.
  4. Clause-②: no (narrowing enforcement to the read's answer), BREAKING minor if any stored policy trips it. The [finding] $ne with 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.

Activity

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

Metadata

Metadata

Assignees

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:enginepriority:p2Medium: important, M3security

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions