Skip to content

os lint crashes with a bare TypeError on a localized labelcheckLabelCase indexes a value I18nLabelSchema does not require to be a string #15880

Description

@os-litant

Found while implementing #15658 (the swallowed-crash defect one module over) and deliberately not folded in: different file, different function, and fixing it here would have deleted the very input that made #15658's crash path reachable. Unassigned and bare, for triage.

What

packages/cli/src/commands/lint.ts, checkLabelCase (located by content):

function checkLabelCase(label: string, path: string): LintIssue | null {
  if (label && label[0] !== label[0].toUpperCase()) {

The parameter is typed string, but every call site reaches it through any-typed config walking, and the spec does not require a label to be a string. I18nLabelSchema (packages/spec/src/ui/i18n.zod.ts) is a union of z.string() and an inline locale map, and it is the label primitive the whole ui/ tree imports — app.zod.ts, view.zod.ts, page.zod.ts, dashboard.zod.ts, chart.zod.ts, report.zod.ts, action.zod.ts, bulk-action.zod.ts, component.zod.ts.

For the locale-map form, label[0] is undefined and undefined.toUpperCase() throws. There is no guard: the throw leaves lintConfig and lands in the command's catch-all, which prints the message and exits 1.

Driven

Both faces, on a config whose only content is a localized app label. Fixture: apps: [{ name: 'todo_app', label: { en: 'Todos', 'zh-CN': 'DAI-BAN' } }] (a real CJK string was used; transliterated here only to keep this body plain ASCII), plus a minimal manifest. Run against the CLI built from this tree.

$ os lint --score
  ✗ Cannot read properties of undefined (reading 'toUpperCase')
EXIT=1

$ os lint --score --json
{"error":"Cannot read properties of undefined (reading 'toUpperCase')","conversions":[]}
EXIT=1

The stack is schema-valid: ObjectStackDefinitionSchema.safeParse returns success: true on it. So this is not a bad-input diagnostic degrading badly — it is a supported authoring shape the linter cannot walk.

How wide, measured rather than reasoned

A single-value mutation sweep over examples/app-todo's normalized config (every path, eight hostile values, 15,728 mutations) kept 3,064 mutations schema-valid. Exactly two of those crash lintConfig, and both are this defect:

HIT @ apps.0.label       := {}  -> TypeError: Cannot read properties of undefined (reading 'toUpperCase')
HIT @ views.0.list.label := {}  -> TypeError: Cannot read properties of undefined (reading 'toUpperCase')

Positive control in the same run: 241 schema-INVALID mutations also crash lintConfig, so the detector fires and the two hits above are not an artefact of an always-empty scan. A second sweep over the four bundled eval-corpus fixtures (2,745 mutations, 241 schema-valid survivors, control 314 invalid crashers) found zero — those fixtures declare no apps and no views.

Measured asymmetry worth carrying into triage: objects[].label is z.string() and rejects the map outright (objects.0.label: Invalid input: expected string, received object), while apps[].label accepts it. So an author who localizes an object label is told so; one who localizes an app label crashes the linter.

Blast radius

Not decided here

Whether the repair is to guard checkLabelCase on typeof label === 'string' (the rule then says nothing about localized labels), or to resolve the map's default-locale entry and case-check that (the rule keeps working, and would need to decide which entry is authoritative), is a product call this finding does not make. The asymmetry above may itself be the thing to settle first: two label carriers in one spec accepting different types is a contract question, not a lint question.

Not deduped beyond one search

One targeted search against this repository, with a positive control in the same session: the control query returned #15658, #15578, #10653, #10123 and #8855, so the channel was live rather than silently empty. The dedup query itself returned nothing open on this subject.

Activity

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions