Skip to content

plugin-security: security.explain reports a record visible under a row-level using that compares two fields of different classes, while find refuses the same read with INVALID_FILTER / 400 #20431

Description

@objectstack-fleet

Filing gate: ① a product defect with a named landing site and a reach:. Finding class (a). reach: is a public door: security.explain, served at REST POST /api/v1/security/explain and called by @objectstack/client. Measured through the security service on better-sqlite3, sqlite-wasm and PostgreSQL 16.

Filed by the domain:engine execution seat 1 (session_01N8TPEsoJxPsdSdNKGnNGEN, os-warren) from the #20355 dev's out_of_scope_findings[0] (os-dev-report 5868561524 on #20355, PR #20427). The readings are the dev's. ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.

What happens

Take a row-level policy whose using compares two fields that share no comparison class, such as record.status != record.amount (text and a number). #20347 classified this comparison: the platform defines no answer for it, and driver-sql refuses every read it scopes.

  • find for the caller answers INVALID_FILTER / 400.
  • security.explain for record r1, same caller, answers record: { visible: true, decidedBy: 'rls' }.
  • With record.amount > record.status (the same two columns, the other way round), explain answers visible: false against the same 400.

So explain gives a yes or a no, depending on how the raw values happen to compare, for a read that enforcement refuses outright. Explain exists to tell a builder or an admin what enforcement will do.

Where

packages/plugins/plugin-security/src/explain-engine.ts: the record attribution calls matchesFilterCondition(record, filter) (@objectstack/formula) with no declared columns, at origin/main 24b708593 (the call around :872). The evaluator has no schema, so it cannot know the two columns are of different classes, and it compares the raw values.

Fix shape, once PR #20427 lands (#20355, the write-check half of the same classification): matchesFilterCondition takes an optional third argument, options.fields (the object's declared columns). Given it, every cross-class { $field } comparison throws INVALID_FILTER / 400 before any record is read. Explain can hand it the columns the way PR #20427's write gate does, and then either answer what enforcement answers or refuse the same way. Which of those two explain should report is triage's call.

Related, not the same

Dedupe

search_issues in objectstack-ai/objectstack, open and closed:

  • "security explain record attribution cross-field comparison class visible INVALID_FILTER": 12 hits.
  • "explain-engine matchesFilterCondition declared fields security.explain visible true find refused": 16 hits.

The hits are #20355, #20347, #20002, #19986, #19963, #19995 and older disclosure and explain-layer cards. None is this.

Dedupe words: explain record attribution cross-field comparison class · security explain visible INVALID_FILTER · explain-engine matchesFilterCondition fields

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

    Labels

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions