Skip to content

plugin-grid: after #6723, ObjectGrid still skips the field-level-security re-check on the authored columns path #6799

Description

@claude

ObjectGrid.generateColumns() re-applies field-level security on two of its
three authored-column paths. After objectui#6723 lands, the AUTHORED columns
path is the one that is left.

Filing, not claiming. Observed while implementing objectui#6723 (the inline-data
half), which was deliberately scoped by the maintainer ruling of 2026-08-29 to
the inline-data path and to that path only.

The remaining asymmetry

generateColumns() has three default paths. Measured on d06059f24 plus the
objectui#6723 branch:

pathreached whenre-applies FLS
authored columns (normalizeColumns(schemaColumns), both the ListColumn[] and the string[] arms)schema.columns present and non-emptyno
inline-data (hasInlineData, authored fields)host passes data and schema.fields is declaredyes, as of objectui#6723
object-schemaeverything elseyes

Evidence on d06059f24:
git grep -n checkField -- packages/plugin-grid/src/ObjectGrid.tsx returns
exactly two lines — one comment and one call — and the call is inside the
fieldsToShow.forEach of the object-schema path. The if (cols) branch above
it returns from either of its two arms before that point and contains no
checkField of its own.

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

Identical to the argument on objectui#6723. ListView, the dominant host,
filters its own effectiveFields through checkField before forwarding, and
that filtered array is exactly what it sends as columns — so through
ListView the gate is applied host-side and this omission is redundant.

ListView.tsx's own hasAuthoredColumns docblock already states the property
as a known fact ("ObjectGrid re-applies FLS only on the DERIVED path, not on
the explicit-columns one"), and objectui#6598 relies on it deliberately: when
the field gate removes every authored column, ListView keeps sending the empty
projection precisely because the grid would not re-check.

The exposure, if any, is the same shape objectui#6723 described: a direct
ObjectGrid schema={{ objectName, columns }} composition, or a future host that
forwards an authored projection unfiltered. Unlike the inline-data case, this
one does not even need the host to supply rows — the columns path runs whether
the grid fetches or not.

Why this is a decision and not a mechanical follow-up

The objectui#6723 ruling took defence in depth over "the host owns it", on the
stated ground that a security invariant must not rest on every future host
having read the docs. That reasoning does not distinguish fields from
columns, which is why this is worth putting in front of triage rather than
assuming either answer. But two things differ enough that it is a separate call:

  1. The declared-key limit needs restating for ListColumn[]. The
    objectui#6723 fix judges only keys the object declares, so host-joined and
    derived keys survive. A ListColumn carries more than a name (label,
    link, action, prefix, width), and a column whose field the object
    does not declare is a legitimate authored derived column. The same limit
    should apply, but "which key is judged" has to be read through
    columnIdentity / resolvesToDataColumn rather than off a bare string.
  2. objectui#6598 pinned a behaviour that assumes the current answer. Its
    hasAuthoredColumns predicate and the test that pins it are written around
    "the explicit-columns path does not re-check". Adding the check does not
    invalidate that predicate — it stays correct for a different reason — but the
    pin's stated rationale would need rewriting in the same change, and that is
    the kind of thing that should be decided rather than done in passing.

Not measured in a browser: this is a code reading plus the ablation run on the
objectui#6723 branch. Severity is deliberately left to triage.

Related: objectui#6723 (the inline-data half, fixed), objectui#6598,
objectui#6677.


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p1security

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions