Skip to content

plugin-grid: ObjectGrid re-applies field-level security only on the object-schema column path, so a host-fed grid with an authored fields projection skips it #6723

Description

@claude

Observed while implementing objectui#6677 (PR #6722), which reordered
ObjectGrid.generateColumns()'s default paths. Filing, not claiming — and NOT
fixed in that PR, which was deliberately scoped to the row-key fallback.

The asymmetry

generateColumns() re-applies field-level security to its columns at exactly
one place, in the object-schema path — packages/plugin-grid/src/ObjectGrid.tsx
(measured on efbd027cb):

if (perms?.isLoaded && schema.objectName
  && !perms.checkField(schema.objectName, fieldName, 'read')) return;

The inline-data path has no equivalent check. Both paths can serve an
object-bound grid, and which one runs depends on who fetched the rows and
whether the author declared a projection:

rows from fields declared path taken grid re-applies FLS
grid fetches no object-schema yes
grid fetches yes object-schema yes
host passes data no object-schema (since PR #6722) yes
host passes data yes inline-data no

So the same object, same declared projection, renders through a code path that
does or does not re-check FLS purely according to who owns the fetch. Before PR
#6722 the third row was in the same position; that PR closed it for the
undeclared case and this is the remainder.

Why it is not obviously exploitable, and why that is not the same as fine

ListView — the dominant host — filters its own effectiveFields through FLS
before forwarding, and its source says so explicitly (ObjectGrid re-applies
FLS only on the DERIVED path, not on the explicit-columns one — so widening
here would be a widening past the field gate). So through ListView the gate is
applied host-side and the grid's omission is redundant.

The exposure, if any, is a host that is not ListView: a direct
ObjectGrid schema={{ objectName, fields }} data={rows} composition, or a future
host that forwards an authored projection without filtering it. The grid would
render a column for a field the principal cannot read, populated from whatever
the host's payload contained.

Not measured in a browser — this is a code reading, and the severity call is
deliberately left to triage rather than asserted here.

The decision behind it

Closing this is not a one-line move, which is why PR #6722 left it alone: it
asks whether the grid's FLS re-check belongs on the AUTHORED projection at all,
or whether authored columns are the host's responsibility. The two paths also
differ in two other ways that would have to be settled together — the schema
path resolves labels through resolveFieldLabel (i18n) and drops names the
object does not declare (if (!field) return;), while the inline path humanises
labels locally and keeps host-joined / derived keys. Converging them without
deciding those would silently drop derived columns.

Related: objectui#6677 (the branch-order half, fixed), objectui#6598.


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