Skip to content

findData's list-query normalizer coerces repeated query parameters without checking arity (the half #6877 could not reach) #7321

Description

@os-help

Observation-class finding, split out of #6877 while implementing it. Unassigned, filed per Prime Directive #10. #6877's PR deliberately did not widen into this — it is in a different package and a different owner's contract.

Why it is a separate card and not part of #6877

#6877 swept packages/rest/src/rest-server.ts's query read points and declared, per handler, which parameters are single-valued. One route was deliberately left ungated: GET /api/v1/data/:object does not read parameters at all — it hands the WHOLE query record through:

const result = await p.findData({
    object: req.params.object,
    query: req.query,
    ...
});

Every parameter's arity for that route is therefore decided by the shared list-query normalizer in packages/metadata-protocol/src/protocol.ts, not by packages/rest. Declaring an arity list at the REST layer would have been one package guessing at another's contract, and the normalizer is genuinely the right home: GET /data/:object, POST /data/:object/query and the runtime dispatcher all flow through it, which is exactly the reason #4181's filter rejection was put there rather than copied per route.

The fact

IHttpRequest.query is Record< string, string | string[] > and the array arm is produced by a real first-party adapter — NodeHttpServer hands ?x=1&x=2 through as ['1','2'], measured over a socket on #6878. The normalizer coerces without checking the arity it was handed. Read directly, packages/metadata-protocol/src/protocol.ts:

if (options.limit != null) options.limit = Number(options.limit);
if (options.offset != null) options.offset = Number(options.offset);

Number(['1','2']) is NaN, so ?$top=1&$top=2 reaches the driver as limit: NaN — the same shape #6928 / PR #7299 just fixed one layer over on GET /api/v1/notifications, where NaN survived the clamp and landed in data.find({ limit: NaN }).

That is one measured line, not a survey. The survey is the work: this normalizer also folds four spellings of the filter slot, a large alias table (pageSize / perPage / take / first / … all rewrite to $top), the $-alias consumption pass, and the leftover-key bucket that lowers unknown keys into field-equality predicates. Each of those needs the same per-parameter single-vs-multi judgement #6877 made for the REST layer, and some of them are genuinely multi-valued ($select, $expand, $searchFields all already accept the array arm on purpose).

Not live today, and exactly why that is temporary

No user hits this now: it takes a client that repeats a parameter, and the production Hono adapter collapses repeats to the first value before any handler runs. #6878's route 2 — ruled adopted on 2026-08-10 — removes that collapse. So the dormancy here has the same expiry date the #6877 surface had, and the same reasoning applies: this is the prerequisite for a decided change, not speculative hardening.

Not filed as a sub-issue of #6877: its fix lands outside #6877's completion scope (different package, different contract owner). Depends on nothing, but its priority is coupled to #6878 route 2.

Dedup

Searched open issues for req.query, findData + normalizer, metadata-protocol + query param, and string[]. Hits: #6307 (the origin, two handlers in package-routes.ts), #6877 (the packages/rest sweep, this card's parent in origin only), #6878 (the adapter divergence). None covers the findData normalizer.


Generated by Claude Code

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions