Skip to content

finding: GET /api/v1/meta/app?id=… — the ?id= filter is inert, the same 3 apps come back for any value #7566

Description

@huangyiirene

Symptom

GET /api/v1/meta/app?id=… ignores the id parameter entirely: the same 3 apps are returned for any value passed, including values that match no app.

Expected: either the filter narrows the response to the named app, or an unsupported parameter is refused rather than silently dropped — the platform has been closing exactly this "silently ignored query parameter" class elsewhere (see below).

Why it matters even though nothing crashes: a caller cannot tell a working filter from a dropped one. A client that asks for one app and renders items[0] gets a plausible, wrong answer, and a bogus id can never 404.

Root cause

Not located by the report — it observed the endpoint's behaviour only. Two shapes to check on the handler for /meta/app: whether id is parsed at all, and whether an unrecognised query parameter reaches a refusal. Filed domain:cli because the /meta route handlers live under the REST/runtime route layer; re-route if the filter is meant to be applied further down.

Related — the same class, being closed one endpoint at a time

Reproduction

  1. Boot the showcase (3 apps published).
  2. GET /api/v1/meta/app?id=<a real app id> → all 3 apps.
  3. GET /api/v1/meta/app?id=<a nonsense value> → the same 3 apps, byte-identical response.

Source

Extracted from the QA run #7514 (framework a86db17, console 09987b68).

Activity

  1. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    Findings triage: resolved the co-applied pm:queue + finding dual state → pm:queue (kept domain:cli as filed — /meta route handlers live in the REST/runtime layer, consistent with the domain table's packages/rest/runtime row). Concrete defect: an inert ?id= filter with a byte-identical response for any value, reproduced, in a class the repo is actively closing endpoint-by-endpoint (#7490, #7350/#7299, #7324/#7396 precedent; open sibling #7527) — dispatchable, not a held observation.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. self-assigned this
    on Aug 11, 2026
  3. os-help commented on Aug 11, 2026

    @os-help
    Collaborator

    Claim — PM loop domain:cli, session session_0158ZQo7LiHSxGWpYKuPq1wu (os-help seat, #6024), wave 6.

    Unblocked: held behind #7526 (/meta surface), which landed as PR #7584 (cc3555e78).

    The card says to follow whichever convention the sibling cards settled on rather than inventing a third — and this seat merged those cards today, so the convention is now concrete:

    So the house answer is both, in that order: refuse malformed, enforce well-formed. ⛔ Do not hand-roll a comparison, ⛔ do not invent new error vocabulary, and ⛔ do not settle for "silently ignored" on the grounds that nothing crashes — the card is right that a caller cannot tell a working filter from a dropped one, and a bogus id can never 404.


    Generated by Claude Code

  4. os-help commented on Aug 11, 2026

    @os-help
    Collaborator

    Done — draft PR #7662 on branch claude/issue-7566-meta-app-id-filter.

    Root cause: nothing on the route had ever read id. The list handler (packages/rest/src/rest-server.ts:4593) narrows by permission (filterAppForUser, invoked at rest-server.ts:4706) and by ?package= / ?object= / ?include=; id appears in none of them. Not a broken filter — a filter that was never written, so the parameter was accepted by the transport, never read, and never refused.

    Fix, both halves of the class:

    • Well-formed ?id= now filters, matching on name — the App document's identity (AppSchema.name, packages/spec/src/ui/app.zod.ts:1288), the same key GET /meta/app/:name addresses. AppSchema declares no id of its own, so there is no second identity to disagree with. Both /meta/app and /meta/apps are covered.
    • Malformed ?id=a&id=b is refused 400 VALIDATION_ERROR, through the same refuseRepeatedQueryParams gate this handler already opens with for ?package= / ?preview= / ?object= / ?include= (packages/rest 的其它 req.query.* 读取点同样把 string | string[] 当字符串用(#6307 的未扩大部分) #6877). Full note in the PR on why the rest-package rule and not runtime/query-param.ts: packages/rest does not depend on @objectstack/runtime, and the twin rule is already applied to this exact line — a second dialect on one handler is what query-allowlist.ts explicitly warns against.

    Empty-vs-404 → empty 200, measured off this surface's siblings rather than chosen: ?package=<no such package> and /meta/view?object=<no such object> both serve an empty list from this handler, and the only meta 404 is the single-item address GET /meta/:type/:name. List routes serve an empty list; the 404 belongs to the item address.

    Ordering: the filter runs after the ADR-0045 §3 publish gate, so ?id=<an unpublished app> answers a non-builder the same empty list as a nonexistent id.

    Verification: 7 new cases in packages/rest/src/meta-app-publish-gate.test.ts, all asserting the returned app names rather than a status code (matching id / non-matching / absent / empty / repeated / one-element array / plural spelling / composition with the publish gate / no-permission caller / a non-app type for the scope fence). Reverse-verified: with the handler change reverted, 7 of 16 fail. pnpm --filter @objectstack/rest test 1412 passed, pnpm lint clean, pnpm typecheck 126/126 — output pasted in the PR.

    Left deliberately out of scope: ?id= on other metadata types keeps being ignored, as before. Changeset: .changeset/meta-app-id-filter.md (@objectstack/rest patch).


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions