Skip to content

[finding] getUiView list branch serves fields declared hidden when the field name is one of its nine priority names — declared, not enforced #13259

Description

@os-trump

Found out of scope by the #13214 tenancy-limb dev (PR #13258) while measuring the blast radius of GET /api/v1/ui/view/:object/:type. ⛔ Reported, not touched — the repair is in a different package from that card and is a different defect class.

Ungraded and unrouted. Severity not judged here. Duplicate-checked with one targeted search before filing (it returned two unrelated closed items, #10001 and #7494, so the channel was live and the result is a reading rather than an unverified zero).

Measured

packages/metadata-protocol/src/protocol.ts, getUiView, the type === 'list' branch:

const priorityFields = ['name','title','label','subject','email','status','type','category','created_at'];
let columns = fieldKeys.filter(k => priorityFields.includes(k));
if (columns.length < 5) {
    const remaining = fieldKeys.filter(k => !columns.includes(k) && k !== 'id' && !fields[k].hidden);
    columns = [...columns, ...remaining.slice(0, 5 - columns.length)];
}

!fields[k].hidden is applied to the fill pass only. The priority pass has no such filter, so a field declared hidden: true whose name happens to be one of those nine is emitted as a list column — with its label.

Driven, not read off the source (PR #13258, packages/rest/src/ui-view-route-tenancy.measurement.test.ts, section 3). One object carrying two hidden fields of different kinds:

  • beta_secrethidden: true, not a priority name ⇒ dropped, as expected;
  • statushidden: true, and a priority name ⇒ served, { field: 'status', label: 'Beta Status', sortable: true }.

The form branch of the same function filters all hidden fields uniformly (.filter(k => ... && !fields[k].hidden)), so the two branches of one producer disagree about what hidden means.

Why this is worth a card rather than a comment

  1. Declared is not enforced (Prime Directive chore: version packages #10). hidden reads as a floor and is one for eight of nine spellings; the ninth class is decided by whether the author happened to pick a name from an internal presentation heuristic. Nothing at authoring time says so.
  2. The affected names are the likely ones. email, status, type, category, subject, title are exactly the field names an author reaches for, so the failing case is not exotic.
  3. ⚠️ It compounds with [finding] GET /api/v1/ui/view/:object/:type resolves a UI view with no identity resolution and no anonymous-deny at the REST seam #13214. That card is measuring anonymous and cross-environment reachability of this same route. If that decision lands on "the route is deliberately public", this defect decides part of what "public" then discloses. ⛔ That is a note about ordering, not a claim about [finding] GET /api/v1/ui/view/:object/:type resolves a UI view with no identity resolution and no anonymous-deny at the REST seam #13214's outcome, which is a maintainer decision.
  4. An earlier measurement (PR test(rest): measure identity resolution at GET /ui/view/:object/:type — seam, downstream producer, and the ratchet blind spot behind it #13244) drove one hidden field, which was not a priority name, and reported "hidden is dropped by declaration". That reading is true of the field it drove and false of the class — worth recording, because it is the shape that reads as a clearance.

Re-check

git grep -n "priorityFields" -- packages/metadata-protocol/src
pnpm --filter @objectstack/rest exec vitest run src/ui-view-route-tenancy.measurement.test.ts -t "hidden"

⛔ Reverse-check any zero with a term known present in the same file: hidden appears in the form branch of the same function, and getUiView sits directly below auditMetaItem.

Not decided here

Which way it should be repaired — drop hidden fields from the priority pass, or make the priority pass respect a declared column list — is a spec-adjacent call about what hidden means on a view, and is left to triage.

Refs

Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions