Skip to content

Four server-side readers derive platform authority from the NAME in ExecutionContext.positions — same species as #15948's blocked escalation, and already reachable on main #15981

Description

@os-warren

The four sites

Each derives platform-admin authority from a name in ExecutionContext.positions rather than from the capability rung:

file line
packages/plugins/plugin-sharing/src/sharing-rule-service.ts :254
packages/plugins/plugin-approvals/src/approval-service.ts :976
packages/runtime/src/domains/activation-gate.ts :149
packages/plugins/plugin-security/src/explain-engine.ts :117

⚠️ These are pre-existing and reachable on main today. They were not introduced by PR #15948 and are not fixed by it — the resolver section that populates ExecutionContext.positions is untouched by that PR. ⇒ Do not read this as fallout from that change; read it as the same defect, found while looking at that change.

Why this is the same species as a blocked escalation

PR #15948 was failed in Clause-② review for deriving isPlatformAdmin as an array-name read. The reason that was blocking, measured on the real pipeline: sys_user_position is apiEnabled and its names are unconstrained, so a tenant can mint an ADR-0057 D4 row spelling platform_admin. A plain org member holding such a row passed the /admin/* mount gate while the capability rung said false.

Core's own resolver states the rule, at resolve-authz-context.ts:1125-1129:

Read the RUNG — never positions.includes(...); an ADR-0057 D4 sys_user_position row may spell that very name.

That is a comment, not a gate. It did not stop the one PR #15948 nearly shipped, and it has not stopped these four.

ctx.posture is already carried on the same context for these sites to read instead, so the correct value is present at every one of them — this is a wrong read of available data, not missing data.

What the taker owes, in order

  1. Drive each of the four, don't read them. For each: does a sys_user_position row spelling the built-in name actually change that site's decision? ⚠️ A site may be unreachable for other reasons, or may already be gated upstream — that would be a finding, not a failure, and it must be measured per site rather than assumed from the shape.
  2. Do not verify this class by string search. PR fix(plugin-auth)!: session payload positions[] is the security axis, not the better-auth role scalar #15948's instance was written through the constant BUILTIN_IDENTITY_PLATFORM_ADMIN, so a grep for the string literal returned zero on both the broken and the fixed head. I made exactly that mistake; a failing control is what caught it. Read the assignment, or drive it.
  3. Fix by reading the rung, matching what PR fix(plugin-auth)!: session payload positions[] is the security axis, not the better-auth role scalar #15948 landed: grants.posture === 'PLATFORM_ADMIN', which is byte-for-byte what hasPlatformAdminStanding returns.
  4. Pin each site with a mutated three-way-agreement test — the payload alias, the site's own gate, and the rung must agree with the D4 row present — and state each pin's population. ⭐ Round 1 of fix(plugin-auth)!: session payload positions[] is the security axis, not the better-auth role scalar #15948 got through the author's own check because a green pin (platform-admin-standing.consolidation.test.ts PIN 6) was cited as "the mechanical proof that the derivation agrees with both gates shape for shape" while having no case where a D4 row spells the built-in name.

Scope note

⚠️ Four packages, and this seat is domain:services. runtime/src/domains/activation-gate.ts may belong to another lane; split if so. ⛔ Do not let a lane boundary leave one of the four unfixed while the other three land — a single remaining name-read reopens the hole.

Dedup

Checked by the reviewer that found them: no existing card matches. The nearest, #11978, is a different census.

Related

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions