Commit 55b1a28
committed
fix(sharing,approvals,runtime,security): platform authority is the posture RUNG, never a name in positions[]
Four server-side readers derived platform-operator authority from a NAME in
`ExecutionContext.positions` rather than from the ADR-0095 capability rung.
`positions[]` is the security axis, so it carries ADR-0057 D4
`sys_user_position` names alongside the built-ins. That table is `apiEnabled`
with unconstrained `position` values, so a tenant could mint a row spelling
`platform_admin` for one of their own users: `resolveUserAuthzGrants` pushes the
name straight onto `grants.positions`, while `grants.posture` is derived from
the unscoped `admin_full_access` grant and nothing else and correctly stays
`MEMBER`. Every reader of the name therefore answered true for a principal
enforcement treats as an ordinary member.
`resolve-authz-context.ts` states the rule at `hasPlatformAdminStanding` --
"read the RUNG, never positions.includes(...)" -- but a comment is not a gate,
and it stopped none of these four. Each now tests
`posture === 'PLATFORM_ADMIN'`, byte-for-byte what that predicate returns.
Each site was DRIVEN rather than read, with the D4 row present, before and
after. Three were live; the fourth is reported as what it measured, not as what
the shape suggested:
- plugin-sharing `hasPlatformAuthority` -- the minted row satisfied
`assertResolvableAdminScope`, so an org-less caller holding only the
ORG-scoped `manage_sharing` capability was answered with every tenant's
sharing rules, and could delete platform-global rows.
- plugin-approvals `isOverrideActor` -- already read the rung and then ORed the
name onto it, which is no protection. Because that arm crosses the tenant
wall, the minted row moved another organization's pending request to
`approved`.
- runtime ADR-0126 §5 activation gate -- under a walled posture this is the
only thing between a tenant org admin and the install-wide activation row, so
the minted row reopened #10243 with a durable row behind it.
- plugin-security `derivePosture` -- NARROWER, and measured: the name-read sits
behind an early `ctx.posture` return that `buildContextForUser` always
populates, so the shipping path was already gated and the D4 row never moved
it. What the read did reach was a posture-less hand-built context, where it
made the panel REPORT `PLATFORM_ADMIN` for a MEMBER.
Each site gets a mutated three-way-agreement pin whose population is stated in
its own header, and each contains the case whose absence let #15948 round 1
pass its author's own check: a D4 row that spells the built-in name, with the
name-read, the site's own gate and the rung asserted together.
Six existing fixtures pinned the removed branches and are migrated, not
deleted: four spelled a platform operator as the bare NAME (they now carry the
rung a resolved operator always has, plus a new name-only refusal arm), and two
in `explain-engine.test.ts` asserted the removed derivation directly and are
inverted with the reason recorded in place.
Refs #15981
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y1 parent b3820c3 commit 55b1a28
15 files changed
Lines changed: 1485 additions & 52 deletions
File tree
- .changeset
- content/docs/permissions
- packages
- plugins
- plugin-approvals/src
- plugin-security/src
- plugin-sharing/src
- runtime/src/domains
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
137 | 137 | | |
138 | 138 | | |
139 | 139 | | |
140 | | - | |
| 140 | + | |
141 | 141 | | |
142 | 142 | | |
143 | 143 | | |
144 | 144 | | |
145 | 145 | | |
146 | 146 | | |
147 | 147 | | |
148 | | - | |
| 148 | + | |
149 | 149 | | |
150 | 150 | | |
151 | 151 | | |
| |||
164 | 164 | | |
165 | 165 | | |
166 | 166 | | |
167 | | - | |
| 167 | + | |
168 | 168 | | |
169 | 169 | | |
170 | 170 | | |
| |||
0 commit comments