Skip to content

Nothing binds the last-admin-guard standing-key lists to what the authz resolver actually reads — the correspondence is prose only #8734

Description

@qq9340100

Filed as a finding (recording only, unassigned, ungraded) out of the review of PR #8713 / #8613. Not fixed there deliberately — it is the structural gap that card exposed, not the card's own work.

The observation

packages/plugins/plugin-auth/src/last-admin-guard.ts decides whether to pay for a full "who is still an administrator" enumeration by testing the pending write against three standing-key lists, one of which is:

constPERMISSION_SET_STANDING_KEYS=['name','active']asconst;// was ['name'] before #8613

That list is not an independent design artifact. It is a cache of which columns resolveAuthzContext reads when deriving platform_admin. Its correctness is entirely derivative: if the resolver starts reading a column the list omits, the guard silently stops firing on the one write class that can empty the administrator population.

Nothing mechanical enforces that correspondence. It is maintained by a prose comment and by whoever next edits the resolver happening to remember the guard exists in another package.

Why this is worth a card rather than a comment

It has already failed once, and the failure mode was the dangerous direction — a false assertion left standing in the code:

An authoritative, recently-dated, wrong comment in a security guard is the specific hazard here — it reads as a checked fact to the next author, human or agent.

Shape of a fix (sketch, not a prescription)

The gate wants to answer: does the set of columns the platform-admin derivation reads equal the set the guard treats as standing-bearing? Candidate approaches, in rough order of cost:

  1. A check script that extracts the column literals read along resolveAuthzContext's platform-admin path (packages/core/src/security/resolve-authz-context.ts) and the standing-key lists (last-admin-guard.ts), and fails on asymmetry — the same family as the existing check:authz-resolver.
  2. A shared, single-source declaration both sides import, so the two cannot spell it differently.
  3. Failing either, a test that asserts the correspondence explicitly, so the pin lives somewhere a resolver change will break.

Whichever shape, the property to hold is that a resolver change that starts reading a new column cannot land green while the guard's list omits it.

Note the same reasoning applies to the sibling lists in that file (GRANT_STANDING_KEYS and the membership/identity-table equivalents), which have the same derivative relationship to their readers.

Not in scope

Not asserting the current lists are wrong — after #8613 they are believed correct. This is about the absence of anything that would tell us if they stopped being correct.

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions