Skip to content

finding: lintLivenessProperties throws on a null collection item, against its own "returns findings, never throws" contract #11385

Description

@os-steve

Summary

lintLivenessProperties' docblock states the contract: "Advisory only — returns findings, never throws." The flat TYPE_COLLECTIONS loop does not hold it — it reads item.name off every element of every registered collection without checking the element is an object:

for(constitemofasArray(stack[key])){constname=typeofitem.name==='string' ? item.name : 

Measurement (built @objectstack/lint dist, main @ b863f01 plus PR #11383)

lintLivenessProperties({ agents: [null] })
-> THROWS: TypeError: Cannot read properties of null (reading 'name')

Reproduces for any registered collection, not just agents.

Reachability — why this is a finding and not a bug report

Latent, as far as measured. The rule is declared input: 'parsed' in authoring-rules.ts, so the stack it receives has already been through Zod, and a null element does not survive that parse. The throw is reachable only by a caller handing lintLivenessProperties an unparsed or hand-built stack — which the exported signature (stack: AnyRec) permits and the docblock invites, since a function that promises never to throw reads as safe to call on anything.

What makes it worth recording rather than ignoring: this rule is the advisory tier, whose whole design premise is that it can never fail a build. A throw from an advisory rule is not a warning that is wrong, it is the linter aborting — a strictly worse failure than the one the tier exists to avoid.

Suggested shape

One guard in the flat loop, mirroring what the bespoke translation walk added in #11383 (if (!isRecord(item)) continue; — the helper already exists in the file after that PR). Whether the object/field walk above it wants the same guard is the same one-line question.

Provenance

Found while implementing #11288 (PR #11383): a regression test there authors a deliberately malformed bundle, and on the pre-fix code that fixture failed with this TypeError rather than with the assertion it was written for. The PR's own bespoke walk guards its two new levels; the flat loop is untouched by it and out of that card's scope. No fix attempted here.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions