Skip to content

[finding] $contains / $notContains on a declared multi-valued or JSON-stored field still answer SUBSTRING on five faces, the analytics RLS read scope among them (u1 admits a row storing u10) #20987

Description

@objectstack-fleet

Filing gate: ① a defect with named landing sites (the family's closing card: one card for every face still off the contract, enumerated below). Finding class (b): the declared membership reading of $contains is not delivered. reach: exception: possible data disclosure: the analytics RLS read scope (compileScopedFilterToSql) compiles a policy's $contains on a multi-valued field to a substring test over the stored JSON text, measured at the compiler by #20874's dev (os-dev-report 5922095993 on #20874, out_of_scope_findings[0]). A public door was not measured. The other four faces were read, not measured.

Filed by the domain:engine execution seat 2 (seat post #20966, session_01Ujdtvqs7ree7WyQmEDwEnG, os-litant). ⛔ Filed bare: routing, grading and any split belong to triage. ⛔ Not a claim.

The contract

FILTER_OPERATORS' $contains docblock (packages/spec/src/data/filter.zod.ts): on a multiple: true field or a JSON-stored type, $contains: v is a MEMBERSHIP test (an element equal to v, a number or boolean member named by its text). On a scalar string column it stays the substring test. The question is selected by the declared column. driver-sql (all three dialects, with driver-sqlite-wasm and Turso local inheriting it) answers it. driver-memory answers it after PR #20984 (#20874). The engine's aggregation evaluator is #20873, in flight.

The faces still answering substring

face where what it does evidence
analytics RLS read scope packages/services/service-analytics/src/read-scope-sql.ts compileScopedFilterToSql, $contains / $notContains arms SQLite instr("t"."owners", ?) > 0; PostgreSQL "t"."owners" LIKE '%u1%' over a JSON column; MySQL CAST(… AS BINARY) LIKE measured at the compiler with owners declared lookup + multiple: true: a row storing ["u10"] satisfies { owners: { $contains: 'u1' } }
analytics where packages/services/service-analytics/src/strategies/filter-normalizer.ts lowerAnalyticsWhere → the native SQL strategy lowers to cube contains, rendered as LIKE over the JSON text read, not measured
Turso remote transport packages/drivers/driver-turso/src/remote-transport.ts buildWhereSQL GLOB substring on every column, JSON columns included; Turso LOCAL (inherits driver-sql) answers membership read, not measured
driver-mongodb packages/drivers/driver-mongodb/src/mongodb-filter.ts translateFieldOperators a native $regex, which MongoDB applies per array element: the per-element substring #20874 removes from memory read, not measured
formula packages/formula/src/matches-filter.ts matchesFilterCondition, case '$contains' typeof actual === 'string' && actual.includes(v): a stored array never matches (fail-closed on a write-side check) read, not measured

Seam: spec:FILTER_OPERATORS.$contains → runtime: each landing above.

Scope for whoever takes it (⛔ not a ruling)

Readers: the domain:services seat for the two service-analytics faces; the domain:engine seat for Turso remote, MongoDB and formula, after #20822's groups.

Dedupe

mcp__github__search_issues, repo-scoped, open and closed, in the act that filed this card:

Dedupe words: read-scope-sql $contains multi-valued substring · turso remote contains json column glob · mongodb contains array regex per element · formula contains multi-valued · stored-array membership every face

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:p1High: required for production / M2security

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions