Measured by the domain:services seat while taking #10348's measurement-only dispatch. Filed by the PM because the measuring seat hit API rate limit already exceeded on every attempt and declined to file blind without a duplicate search — that search has now been run (nothing open).
⛔ Disclosure discipline: conclusions and their in-repo basis only. No reproduction recipe. (#9391/#9653/#9756/#9714/#10069 family.)
What was compared
#10348 asked whether three near-identical id-shaped platform-admin reads should be consolidated. Two survive (isOrgOrPlatformAdmin was deleted by #10390 today). The card asserted "all three do the same two reads".
That is overturned. The two plugin-auth spellings agree with each other byte-for-byte, and disagree with the declared authority — packages/core/src/security/resolve-authz-context.ts, whose own header states that every entry point must resolve authorization through it and never re-read sys_*_permission_set itself — on three axes. Two of those axes are live.
The two live axes
Both auth-manager.ts judges — isPlatformAdminUserId (:4420, which authorizes /sso/register and, via applyPlatformAdminImpersonation, /admin/impersonate-user) and the customSession closure (:2961) —
① apply no ADR-0091 validity window (isGrantActive) and no ADR-0049 active check (isRowActive) to the grant or to the permission-set row. The declared authority applies both before any derivation.
⇒ An expired or deactivated grant still authorizes those gates.
② resolve admin_full_access by matching a name over an unfiltered, unordered find('sys_permission_set', { limit: 50 }) page.
⇒ An environment with more permission sets than that page holds can lose the row and demote every platform admin at once.
Why this is a defect and not a style difference — the corroboration is all in-repo
None of this rests on an outside standard. This repository already does it correctly, in four places, one of them inside the same package:
| |
|---|
plugin-auth's own last-admin-guard.ts | already imports isGrantActive / isRowActive from @objectstack/core — for the same table and the same permission-set name |
ensure-default-organization.ts | already spells the same lookup as { where: { name: 'admin_full_access' }, limit: 1 } — targeted, not a page scan |
last-admin-guard.ts's scan() | deliberately reads maxScan + 1 and refuses rather than truncating — the posture axis ② is missing |
packages/qa/dogfood/test/authz-conformance.matrix.ts:305 | enumerates "every reader that turns a window-carrying row into authority" |
⭐ That last row is the sharpest part of the finding: auth-manager.ts's judge is a reader of exactly that kind and is absent from the enumeration. So the conformance matrix that exists to catch this class does not contain the instance — the gap is invisible to the very inventory built to see it. That makes the enumeration incomplete too, independently of the two judges.
Direction of the repair
Tightening — fewer principals authorized (an expired or deactivated grant stops authorizing; a targeted lookup stops silently missing the row). It is not a loosening and does not need the maintainer floor on that account.
⚠️ But it does change callers' verdicts, which #10348's dispatch placed behind a hard stop, so it is filed rather than fixed.
Relationship to #10348
#10348 is the consolidation card and its thesis was confirmed, not refuted: N copies of an authorization predicate drift, and the drift is invisible while the copies exist. This finding is what the drift turned out to be. The two are best taken together — consolidating onto resolve-authz-context.ts (option C on #10348) subsumes this defect, because the authority already applies both checks and already spells the lookup targeted.
⚠️ Consolidation also carries a separate open question that is a genuine loosening and is the maintainer's: see #10348's measurement of the withSystemReadContext wrapper on the customSession site.
Refs
Filed unassigned, security + finding, no pm:queue — grading is triage's field.
Measured by the
domain:servicesseat while taking #10348's measurement-only dispatch. Filed by the PM because the measuring seat hitAPI rate limit already exceededon every attempt and declined to file blind without a duplicate search — that search has now been run (nothing open).⛔ Disclosure discipline: conclusions and their in-repo basis only. No reproduction recipe. (#9391/#9653/#9756/#9714/#10069 family.)
What was compared
#10348 asked whether three near-identical id-shaped platform-admin reads should be consolidated. Two survive (
isOrgOrPlatformAdminwas deleted by #10390 today). The card asserted "all three do the same two reads".That is overturned. The two
plugin-authspellings agree with each other byte-for-byte, and disagree with the declared authority —packages/core/src/security/resolve-authz-context.ts, whose own header states that every entry point must resolve authorization through it and never re-readsys_*_permission_setitself — on three axes. Two of those axes are live.The two live axes
Both
auth-manager.tsjudges —isPlatformAdminUserId(:4420, which authorizes/sso/registerand, viaapplyPlatformAdminImpersonation,/admin/impersonate-user) and thecustomSessionclosure (:2961) —① apply no ADR-0091 validity window (
isGrantActive) and no ADR-0049 active check (isRowActive) to the grant or to the permission-set row. The declared authority applies both before any derivation.⇒ An expired or deactivated grant still authorizes those gates.
② resolve
admin_full_accessby matching a name over an unfiltered, unorderedfind('sys_permission_set', { limit: 50 })page.⇒ An environment with more permission sets than that page holds can lose the row and demote every platform admin at once.
Why this is a defect and not a style difference — the corroboration is all in-repo
None of this rests on an outside standard. This repository already does it correctly, in four places, one of them inside the same package:
plugin-auth's ownlast-admin-guard.tsisGrantActive/isRowActivefrom@objectstack/core— for the same table and the same permission-set nameensure-default-organization.ts{ where: { name: 'admin_full_access' }, limit: 1 }— targeted, not a page scanlast-admin-guard.ts'sscan()maxScan + 1and refuses rather than truncating — the posture axis ② is missingpackages/qa/dogfood/test/authz-conformance.matrix.ts:305⭐ That last row is the sharpest part of the finding:
auth-manager.ts's judge is a reader of exactly that kind and is absent from the enumeration. So the conformance matrix that exists to catch this class does not contain the instance — the gap is invisible to the very inventory built to see it. That makes the enumeration incomplete too, independently of the two judges.Direction of the repair
Tightening — fewer principals authorized (an expired or deactivated grant stops authorizing; a targeted lookup stops silently missing the row). It is not a loosening and does not need the maintainer floor on that account.
Relationship to #10348
#10348 is the consolidation card and its thesis was confirmed, not refuted: N copies of an authorization predicate drift, and the drift is invisible while the copies exist. This finding is what the drift turned out to be. The two are best taken together — consolidating onto
resolve-authz-context.ts(option C on #10348) subsumes this defect, because the authority already applies both checks and already spells the lookup targeted.withSystemReadContextwrapper on thecustomSessionsite.Refs
admin_full_accesschannel)Filed unassigned,
security+finding, nopm:queue— grading is triage's field.