Skip to content

approvals: listRequests' free-text filter pushes payload_json: { $contains: q } into the engine — a probe oracle over snapshot contents the caller may not read #11040

Description

@os-warren

Found while implementing #10749 (read-time redaction of the approval payload snapshot). Filed rather than fixed there: #10749's scope is the serve path, and this is a predicate path whose fix changes search semantics.

Observation

ApprovalService.listRequests accepts a free-text q and pushes it down as a filter on the snapshot column:

{ payload_json: { $contains: q } }

payload_json holds the submitted record's full raw row. After #10749 the snapshot is redacted on the way out, keyed on the reading caller — but the filter is evaluated by the driver against the stored, unredacted column, before anything is served. So a caller who is correctly prevented from seeing a restricted field can still probe it: submit a candidate value, observe whether the request comes back, repeat. Row membership is the oracle; no restricted byte is ever rendered.

Why this is a real shape rather than a theoretical one

The platform already recognises exactly this class and closes it elsewhere. maskingRule (#8993) makes a field that a caller sees masked non-filterable / non-sortable / non-groupable / non-aggregatable, rejected loudly, and the spec states the reason in those terms — "otherwise equality probes reconstruct the hidden span". The approval search path is the same probe against the same declarations, reached through a different column.

Why it was not fixed on #10749

Closing it is not a redaction change:

  • the filter cannot simply be dropped — free-text search over the snapshot is a shipped capability of the approvals inbox;
  • narrowing it to the caller's readable fields requires the filter to be decomposed per field, which a $contains over a serialized JSON blob does not express;
  • refusing the query when the caller cannot read every snapshot field would change search behaviour for existing deployments.

Each of those is a design choice with a user-visible consequence, so it wants its own grading rather than riding a redaction PR.

Locator

  • packages/plugins/plugin-approvals/src/approval-service.ts — the free-text branch of listRequests' buildRequestWhere (search around the payload_json / $contains pair; the surrounding comment describes it as "Free-text search, pushed down").

Notes

  • Not user-reported; found by inspection while inventorying the column's consumers.
  • No assignee — recording, not claiming.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions