Filed, not fixed: the sites are in objectstack-ai/objectui, outside #10288's file
surface. 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
| file | call | dropped key |
|---|
apps/console/src/sdui-workbench-preview.tsx:65 | find(objectName, { top: 200 }) | top |
packages/app-shell/src/views/ObjectView.tsx:1561 | find(…, { limit: 0 }) | limit |
packages/app-shell/src/views/metadata-admin/AssignedUsersSection.tsx:109 | find('sys_permission_set', { $filter: {…}, limit: 1 }) | limit |
content/docs/guide/react-pages.md:129 | find(…, { 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 #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#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 #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
- objectstack#10288 — the same two defects in
examples/app-showcase, fixed there. - objectstack#10469 — the same
.records misread in objectstack's own react-pages guide. - objectui#4734 / objectstack#7147 — the
options: { $top } half, already gated.
Filed, not fixed: the sites are in
objectstack-ai/objectui, outside #10288's filesurface. Census taken at objectui
c40f3b8ca21ddc19e05682f1719fcf16d4ce7fba(objectstack pins
9a3daf8d37ad973a621e5edd276fe32467f90684; re-confirm at the pinbefore acting).
The class
QueryParams(packages/types/src/data.ts) declares only$-prefixed keys, andObjectStackAdapter.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.
QueryParamsalso carries an index signature, sothe 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 inits
client.test.ts), so an absenttopreturns the entire match set. A dropped cap istherefore invisible until the object is large.
The four live sites
apps/console/src/sdui-workbench-preview.tsx:65find(objectName, { top: 200 })toppackages/app-shell/src/views/ObjectView.tsx:1561find(…, { limit: 0 })limitpackages/app-shell/src/views/metadata-admin/AssignedUsersSection.tsx:109find('sys_permission_set', { $filter: {…}, limit: 1 })limitcontent/docs/guide/react-pages.md:129find(…, { filters: [['status','=','open']] })filtersObjectView.tsx:1561'slimit: 0is worth reading twice:$top: 0is honoured end toend as "no records" (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:109is a one-line-away neighbour of three correct calls(
$top: 500,$top: 200,$top: 1000on L124/131/136) — the shape a reviewer's eyeslides over.
sdui-workbench-preview.tsx:66also readsall.recordsoff the result, with no.dataarm.
find()resolves to aQueryResult(data, neverrecords), so that previewlists nothing regardless of the cap.
skills/objectui/guides/data-integration.md:268publishes the same
return result.records;to skill consumers.Why a rule, and which one
eslint-rules/no-query-params-under-options.jsalready gates the sibling shape —find(obj, { options: { $top: 100 } })— and its own header makes the argument formechanising 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#7137;
object-kanban, objectui#4025).The rule bans a
$-prefixed key underoptions. It does not look at a bare key atthe 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/findOnecall, the fix is a secondpredicate, 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 anadapter.find/dataSource.find/…findOnecall. Not "any unprefixed key" — adapterslegitimately take adapter-specific params, which is why the index signature exists.
An objectstack-side gate was considered for #10288 and rejected:
QueryParamsisobjectui'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
examples/app-showcase, fixed there..recordsmisread in objectstack's own react-pages guide.options: { $top }half, already gated.