Skip to content

A typo'd key inside a page component's properties is accepted in silence — the props bag is a passthrough record and the SDUI props gate has no schema to dispatch for object-* blocks #7751

Description

@huangyiirene

Symptom

An author writes a misspelled key inside a page component's properties, and nothing anywhere complains: the spec parses it clean, it is stored, the renderer ignores it, and the author gets a success receipt for configuration that does nothing.

The live specimen from this run is the showcase's personal work queue (#7750): object-grid authored with filters: (plural) where the renderer reads filter. The wire carried no $filter and the grid listed every row. But the specimen is incidental — this card is about the class.

Scope, stated precisely

Carried verbatim from the run, because the specimen is easy to over-escalate:

Scope, stated precisely: this is not an authorization bypass. The unfiltered read is still RLS-constrained, so the caller sees only rows they may see (admin sees all because admin may see all). The defect is that a declared personal-scope filter silently never applies — and that a typo'd key is accepted without complaint. The second half is the more general problem.

Root cause — two layers, both open for this block family

1. The schema does not judge the bag.PageComponentSchema.properties is z.record(z.string(), z.unknown()). PageComponentSchema itself has been .strict() since ADR-0089 D3a, but strictness does not recurse: it closes the component node's own keys and leaves everything under properties unjudged. This is documented, not accidental — see packages/spec/src/stack.zod.ts:817 ("Containers nest through a loose record") and the header of the lint below.

2. The gate that exists cannot see this block. ADR-0078 / #5068 added packages/lint/src/validate-component-props.ts, which dispatches on the component's type and judges properties against that type's props schema. Two reasons it is silent here:

  • It is warning-only by design, pending an inventory — its own header explains why ("Wiring the parse is the precondition for enforcement, not the enforcement").
  • More decisively, it skips unregistered types silently:
const schema = PROPS_SCHEMAS[type];
if (!schema) continue; // "Unregistered type — skipped silently … the majority
// of authored nodes are SDUI blocks this map does not carry."

And ComponentPropsMap (packages/spec/src/ui/component.zod.ts:987-1033) carries 31 entries, none of them an object-bound block: page:*, record:*, nav:*, global:*, ai:* and element:* are declared; object-grid, object-metric, object-kanban, object-calendar and the rest of the object-* family are absent. The string object-grid does not appear anywhere in packages/spec.

So for the block family that carries the platform's data-bound authoring surface, both layers are open: the schema passes the bag through, and the lint has nothing to dispatch.

Stale-premise check: re-verified on objectstackorigin/main (00e9196) — the passthrough record, the silent-skip branch, and the 31-entry map without any object-* key are all present as described.

Why this is filed as a finding rather than a fix

The direction is a real choice and it touches a published surface:

  • A — declare the object-* blocks in ComponentPropsMap. Gives the existing lint something to dispatch and gets did-you-mean suggestions for free (filtersfilter is exactly the rename shape the walker already emits). Cost: the spec takes on props schemas for blocks whose implementation lives in objectui, and the two must then be kept in parity — the same coupling check:react-declaration-parity already manages for other surfaces.
  • B — leave the map as-is and close the loop on the objectui side, where the block's inputs are declared. objectui#4041 already did this for object-grid specifically (the plural spelling was retired and a spelling pin added), but that is per-block work with no gate making it exhaustive.
  • Either way, the warning→error upgrade for component-props-unknown-key remains gated on the inventory SDUI 组件 props 没有解析闸门:PageComponent.properties 是开放 record,ComponentPropsMap 的 29 个站点从不被 parse(#4001 批 17 的 no gate 判定) #5068 describes.

Neither should be picked by an implementer without a ruling — recorded here so the decision is made once for the whole block family rather than one typo at a time.

Reproduction

  1. Author any page whose component is an object-* block and add an undeclared key inside properties (e.g. filters: on object-grid).
  2. Build / publish — no error, no warning: PageComponentSchema parses the bag, and validateComponentProps skips the type.
  3. Render the page — the key is spread onto the renderer and ignored there.
  4. Control: put the same undeclared key one level out (on the component node rather than inside properties) — ADR-0089 D3a strictness rejects it, proving the gate exists and stops at the bag's edge.

Source

Extracted from the QA run #7695 (framework 92f26f7, console 09987b680).

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions