Two grant() input-hygiene gaps. grant() is owner-only, so neither is an external hole — both are silent-misfire / foot-gun territory.
F4 — grant actions are validated as bare strings
_grant@1's schema is actions: array of string (stack.ts:1566-1570); grant() (stack.ts:1475-1490) writes them without checking against the GrantAction union (types.ts:215-222). A typo'd action ('read_any', 'read-all') or a malformed typeId is stored silently and simply never matches at check time (hasGrant compares against the requested action) — a permission that looks granted but isn't, with no error at grant time or use time.
Fix: validate each action in grant() against the GrantAction set and throw StackValidationError on an unknown one. (The schema language has no enum field-kind; explicit validation in grant() is the cheap fix now. An enum scalar kind is a possible future addition, out of scope.) Optionally validate the typeId is a well-formed versioned/base id.
F5 — grants on system types are a privilege-escalation foot-gun
A create or update-any grant on _grant@1 lets the grantee mint their own grants; a default (any-authenticated) grant on it is self-service escalation for anyone. Same shape for _config@1. Only the owner can call grant(), so this is owner-error, not an external attack — but grant() can cheaply refuse it, matching the "privilege-bearing verbs stay owner-only" principle (#67).
Decided: refuse grants on the _grant and _config families outright (throw StackValidationError). No override flag — there's no legitimate reason to delegate grant-creation or config access at this scale. The refusal is narrow: _grant and _config only. Other reserved types stay grantable — _attachment create grants are load-bearing (every uploader needs one), and _entity/_group create grants are plausibly legitimate.
Work items
Refs
#57 (grant model), #58 (group roles), #67 (_config protection / privilege-bearing-verbs-owner-only). From docs/design-assessment-2026-07.md §F4/§F5.
Two
grant()input-hygiene gaps.grant()is owner-only, so neither is an external hole — both are silent-misfire / foot-gun territory.F4 — grant actions are validated as bare strings
_grant@1's schema isactions: array of string(stack.ts:1566-1570);grant()(stack.ts:1475-1490) writes them without checking against theGrantActionunion (types.ts:215-222). A typo'd action ('read_any','read-all') or a malformedtypeIdis stored silently and simply never matches at check time (hasGrantcompares against the requested action) — a permission that looks granted but isn't, with no error at grant time or use time.Fix: validate each action in
grant()against theGrantActionset and throwStackValidationErroron an unknown one. (The schema language has no enum field-kind; explicit validation ingrant()is the cheap fix now. Anenumscalar kind is a possible future addition, out of scope.) Optionally validate thetypeIdis a well-formed versioned/base id.F5 — grants on system types are a privilege-escalation foot-gun
A
createorupdate-anygrant on_grant@1lets the grantee mint their own grants; a default (any-authenticated) grant on it is self-service escalation for anyone. Same shape for_config@1. Only the owner can callgrant(), so this is owner-error, not an external attack — butgrant()can cheaply refuse it, matching the "privilege-bearing verbs stay owner-only" principle (#67).Decided: refuse grants on the
_grantand_configfamilies outright (throwStackValidationError). No override flag — there's no legitimate reason to delegate grant-creation or config access at this scale. The refusal is narrow:_grantand_configonly. Other reserved types stay grantable —_attachmentcreate grants are load-bearing (every uploader needs one), and_entity/_groupcreate grants are plausibly legitimate.Work items
grant(): validateactionsagainstGrantAction; throwStackValidationErroron unknowngrant(): refuse_grant/_configtarget types (throwStackValidationError)_grant@1/_config@1rejected; grant on_attachment@1still allowedRefs
#57 (grant model), #58 (group roles), #67 (
_configprotection / privilege-bearing-verbs-owner-only). Fromdocs/design-assessment-2026-07.md§F4/§F5.