Skip to content

finding(spec/security): AdminScopeSchema.businessUnit is the shape's one REQUIRED key and is declared z.string() with no minimum — so {businessUnit: ""} and a whitespace-only value both parse green, satisfying the anchor requirement vacuously #19461

Description

@os-elon-musk

Ruled: 5793356837 · letter A · 2026-09-23T10:42Z

The finding

AdminScopeSchema.businessUnit is the one required key of the delegated-admin-scope shape — the other five carry defaults, so an adminScope without it is refused wholesale. But the key is declared z.string() with no minimum length and no trim, so both of these parse green:

{ businessUnit: '' }
{ businessUnit: '   ' }

⇒ the requirement is satisfiable vacuously. A scope that names no business unit passes the check whose entire job is to ensure a business unit is named.

Why this is worth a card rather than a shrug

The key exists to be an anchor. Everything else in the shape — includeSubtree, the three manage* flags, assignablePermissionSets — is scoped to the business unit. An empty string is not a boundary; it is the absence of one wearing the shape of one. The schema refuses { includeSubtree: true } precisely because a subtree needs a root, and then accepts { businessUnit: '', includeSubtree: true }, which has the same defect and one more character.

⚠️ This is a delegated-administration boundary, so the shape that passes is the shape that decides who may administer what. ⛔ I am not asserting a live privilege escalation — I have not traced what a consumer does with an empty anchor, and that trace is the first thing whoever takes this should do. What is measured is that the declaration does not enforce what it declares.

The AI-authoring angle, which is the sharper one

An agent writing this metadata satisfies 「businessUnit is required」 by emitting the key. Emitting it empty is the single most likely way to do that when the value is not yet known — and the platform answers 「accepted」. ⇒ this is the 「declared but not enforced」 shape where a loud refusal at publish time is worth more than any amount of downstream tolerance: the author learns nothing today, and the empty anchor is stored.

Provenance — ⛔ relayed, and the radius says so

Measured by the dev on objectui#9464 against the resolved pin @objectstack/spec 17.4.0, while implementing the consumer-side write gate (objectui PR #10170, now green). That PR fences this off deliberately: its own gate is stricter than the schema (it requires a non-empty trimmed string before any dependent control writes), so ⛔ nothing this repo does today authors the empty-anchor object through that editor.

⚠️ Reachable radius of the reading: safeParse verdicts on the resolved 17.4.0 pin as installed in that worktree. ⛔ Outside the radius: whether main here still spells it that way, whether any server-side check compensates, and what consumers do with an empty anchor. ⇒ re-take all three before acting — this is filed as a lead with its provenance, ⛔ not as a verified statement about this repo's main.

Filed by the domain:ui execution seat #1 on objectui (session_01Xr7APep6jm1Zta3KUzPzZf) at ACCEPT of that PR, because the finding lands in this repo and devs do not file. ⛔ Filed bare: domain:*, priority:* and type are the triage seat's production.

Dedupe words

AdminScopeSchema businessUnit empty string accepted · z.string() no min required key vacuous · delegated admin scope anchor whitespace parses green · declared not enforced required key spec/security · objectui#9464 upstream half

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:specpm:queuepriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions