Skip to content

Four live adapter.find calls pass an unprefixed query option — and no-query-params-under-options gates only the sibling half of the class #5458

Description

@os-zhuang

Moved from objectstack-ai/objectstack#10470 by the triage seat (file-at-destination rule: every named site and the rule that should gate them live in this repo). Original filed 2026-08-21 by os-zhuang from the objectstack-ai/objectstack#10288 dev seat. Body reproduced verbatim below with bare issue refs qualified.


Census taken at objectui c40f3b8ca21ddc19e05682f1719fcf16d4ce7fba (objectstack pins 9a3daf8d37ad973a621e5edd276fe32467f90684; re-confirm at the pin before acting).

The class

QueryParams (packages/types/src/data.ts) declares only $-prefixed keys, and ObjectStackAdapter.convertQueryParams (packages/data-objectstack/src/index.ts) builds its outgoing options by copying exactly those. Any other key reaches no branch and is dropped — no throw, no warning. QueryParams also carries an index signature, so the type system accepts both spellings equally.

The consequence is an unbounded read, not a truncated one: the platform's GET list route has no default page size (objectstack packages/client/src/index.ts, pinned in its client.test.ts), so an absent top returns the entire match set. A dropped cap is therefore invisible until the object is large.

The four live sites

filecalldropped key
apps/console/src/sdui-workbench-preview.tsx:65find(objectName, { top: 200 })top
packages/app-shell/src/views/ObjectView.tsx:1561find(…, { limit: 0 })limit
packages/app-shell/src/views/metadata-admin/AssignedUsersSection.tsx:109find('sys_permission_set', { $filter: {…}, limit: 1 })limit
content/docs/guide/react-pages.md:129find(…, { filters: [['status','=','open']] })filters

ObjectView.tsx:1561's limit: 0 is worth reading twice: $top: 0 is honoured end to end as "no records" (objectstack-ai/objectstack#6485 pinned it), so if that call means what it says, dropping the key inverts it from no rows to every row.

AssignedUsersSection.tsx:109 is a one-line-away neighbour of three correct calls ($top: 500, $top: 200, $top: 1000 on L124/131/136) — the shape a reviewer's eye slides over.

sdui-workbench-preview.tsx:66 also reads all.records off the result, with no .data arm. find() resolves to a QueryResult (data, never records), so that preview lists nothing regardless of the cap. skills/objectui/guides/data-integration.md:268 publishes the same return result.records; to skill consumers.

Why a rule, and which one

eslint-rules/no-query-params-under-options.js already gates the sibling shape — find(obj, { options: { $top: 100 } }) — and its own header makes the argument for mechanising this family: the mistake type-checks, it publishes, the symptom looks like a data problem rather than a code problem, "so a review catches it once and then misses the next one." It cites two live instances by two authors (object-timeline, objectui#4009 / objectstack-ai/objectstack#7137; object-kanban, objectui#4025).

The rule bans a $-prefixed key under options. It does not look at a bare key at the top level, which is the other half of the same class — and the half with four live sites. Extending it (or adding a sibling rule) is the cheap move: the AST walk already lands on the params object literal of a find/findOne call, the fix is a second predicate, and the contract lives in the same repo as the rule.

Suggested shape, narrow on purpose: flag a static key that is a known query-option name without its $ (top, skip, filter, filters, select, orderby, sort, expand, search, count, limit, offset) in the second argument of an adapter.find / dataSource.find / …findOne call. Not "any unprefixed key" — adapters legitimately take adapter-specific params, which is why the index signature exists.

An objectstack-side gate was considered for objectstack-ai/objectstack#10288 and rejected: QueryParams is objectui's contract, so a checker there would have to hard-code another repo's key list and become a second source of truth for it. The rule belongs next to the type it enforces.

Related

Metadata

Metadata

Assignees

Labels

domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions