Skip to content

$expand is still ungated at SEVEN more buildExpandFields call sites — kanban, tree, ObjectView, map, gallery, timeline and PagePreview, none of which passes a column list #7429

Description

@os-project-manager

Filed unassigned by the domain:ui execution seat (session session_01EMrWaQw3XS5DxTHxp4yRyC) while implementing objectui#7230 / PR #7428. Not fixed there: these sites are outside that card's declared file surface.

Duplicate check run before filing, with a control that fires: the query returned 14 on-topic issues including #7230, #7215 and #7216 (the sibling $select gap). A non-empty on-topic hit set makes the absence of a match a reading rather than a broken query. None of the 14 covers these seven sites.

The scope correction this records

objectui#7230 states that buildExpandFields "is called from five more places, none of them gated". Measured on main at bf244f400, the production call sites are:

So the helper has 13 production call sites, not 7, and #7230's "five more places" undercounts the remainder by seven. This is a counting correction, not a criticism of that card's analysis — its reasoning transfers to these sites unchanged.

Measured

Every line number and count below read from main at bf244f400. checkField / usePermissions counts are whole-file greps, so a 0 means the file has no field-level gate anywhere, not merely none at this call.

site shape checkField in file usePermissions in file
packages/plugin-kanban/src/ObjectKanban.tsx:284 buildExpandFields(objectDef?.fields)no column list 0 0
packages/plugin-tree/src/ObjectTree.tsx:454 buildExpandFields(objectSchema?.fields)no column list 0 0
packages/plugin-view/src/ObjectView.tsx:908 buildExpandFields((objectSchema as any)?.fields)no column list 0 0
packages/plugin-map/src/ObjectMap.tsx:707 buildExpandFields(objectSchema?.fields)no column list 0 0
packages/plugin-list/src/ObjectGallery.tsx:312 buildExpandFields(objectDef?.fields)no column list 0 0
packages/plugin-timeline/src/ObjectTimeline.tsx:199 buildExpandFields(objectDef?.fields)no column list 0 0
packages/app-shell/src/views/metadata-admin/previews/PagePreview.tsx:134 buildExpandFields(schema?.fields)no column list 0 0

⚠️ All seven are the SHARP shape, not the mild one

Not one of them passes a column list. buildExpandFields reads an absent column list as "no column restriction" and falls back to every declared relation on the object, denied ones included:

// packages/core/src/utils/expand-fields.ts
if (columns && Array.isArray(columns) && columns.length > 0) {
  // ...intersect with the visible columns
}
return referenceFieldNames;   // ← every declared relation

So these are not surfaces that merely fail to filter a column list — they have none, and therefore ask the server to resolve the maximum possible relation set. That is the ordinary configuration of each, not a corner of it. It is the same reading objectui#7230 recorded for calendar / gantt / record-detail, and it applies here verbatim.

PagePreview is worth calling out separately only because it is a designer preview surface, where the reviewer may plausibly hold different grants from the page's eventual audience; that is a reason to look, not a claim, and it has not been measured.

Severity — precise rather than alarming

Same grading as objectui#6898, #7215 and #7230: p2, defence-in-depth, not a live disclosure against ObjectStack's own server. The mechanism was read rather than assumed by the #7215 lane:

FieldMasker.maskRecord does delete result[field] and objectql's engine writes the expanded record back under that same key, so one statement removes both; the expansion sub-read itself takes the referenced object's full CRUD + RLS + FLS treatment (objectstack#7626).

It is load-bearing for a non-enforcing backend, and the client-request side is real regardless: these components ask the server to resolve relations the current principal cannot read.

The shape is settled — do not re-derive it

PR #7229 settled it and PR #7428 applied it unchanged at four more sites. The gate goes on buildExpandFields's OUTPUT:

const expandable = buildExpandFields(objectSchema?.fields);
const expand = !perms?.isLoaded
  ? expandable
  : expandable.filter((f) => perms.checkField(objectName, f, 'read'));

Gating the input is impossible here anyway (the call passes undefined), and gating the output makes the "checkField answers false for an undeclared key" trap structurally unreachable — the helper returns only declared reference-bearing fields, so every name judged is declared by construction. An unanswered policy must filter nothing, and perms belongs in the dependency list of whatever effect or memo builds the projection.

Three of these packages will need @object-ui/permissions added to dependencies (plugin-kanban, plugin-tree, plugin-map, plugin-timelineplugin-list and app-shell already have it); check:phantom-deps enforces that.

Not measured

Whether any of these seven is reachable by a principal with a denied lookup in a shipped configuration. They are measured as code shape, exactly as #7230's five were before that card's implementation reproduced each one. The first task should be a failing test per site, on the model of packages/plugin-grid/src/__tests__/expandFls-7215.test.tsx and the five files PR #7428 adds. If a site turns out to be unreachable, that is a finding — record it and pin the behaviour rather than fixing it.

Sizing note for triage

Not necessarily one card. plugin-kanban, plugin-tree, plugin-map and plugin-timeline are lazily loaded by the console and PR #7428 measured their chunks as absent from the eager closure entirely (0 bytes). ObjectGallery lives in plugin-list and PagePreview in app-shell, both of which have eager chunks — so if the framework per-chunk headroom (#7399) is still tight when this is scheduled, the same measure-then-split discipline #7230 carried applies.

Related: objectui#7230 / PR #7428 (the four buildExpandFields sites just gated, plus the dashboard's computeLookupExpand; read its implementation first) · objectui#7215 / PR #7229 (where the shape was settled) · objectui#6898 (the original $select gate) · objectui#7216 (the sibling $select gap in ListView's speculative view bindings).

Activity

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2security

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions