Commit 1c83ca2
fix(plugin-security): stop letting org-admin row count decide whether a platform admin already exists (#17116)
* fix(plugin-security): stop letting org-admin row count decide whether a platform admin already exists
The `already_have_admin` short-circuit read `sys_user_permission_set` with no
`orderBy` and a cap of 50, then applied the predicate that actually decides —
`!organization_id` — client-side to whatever 50 rows the driver returned first.
`admin_full_access` is not only the platform-admin set: every organization-scoped
grant of it writes a row carrying the same `permission_set_id`, so the population
grows with the number of org admins. A tenant with fifty-odd of them filled the
window with rows that all fail the filter, the short-circuit did not fire, a
second unscoped grant was minted, and `claimSeedOwnership` re-owned the seeded
business rows to the newly promoted user — silently.
The read is now two legs, both ordered server-side and bounded, and the bound
warns with the number of rows it examined:
Leg A asks the driver the narrow question (`organization_id: null`), so no
org-admin count can crowd the answer out of a window.
Leg B scans the grant population for the set, ordered and bounded, still
applying the exact client-side predicate.
Leg B is not redundant: `organization_id: ''` is storable and reads back as
`''`, which `!organization_id` counts as unscoped and `where: { organization_id:
null }` does not return — so the card's suggested one-line `where` narrowing
would have RELAXED this guard on its own. Both legs are strictly additive to
what the old read could see, so the guard can only fire more often, never less.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37
* test(plugin-security): pin the already_have_admin guard against the org-admin row count, with its under-cap control
The card's reproduction sketch as a cell rather than a failure: 60
organization-scoped grants plus one unscoped human grant whose row sorts last
must return already_have_admin, and the SAME fixture with 9 organization-scoped
grants must return it too. The under-cap row is the control that proves the
fixture measures truncation and not some other difference between the two
populations.
Also pinned: the `organization_id: ''` legacy holder the narrowed read alone
could not have seen; that usr_system still never counts; the reported
adminGrantRowsExamined; and the ceiling warning with its under-ceiling control.
Counts examined rows by identity rather than by read, so the two legs' overlap
does not inflate a number that calls itself rows examined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37
* chore(plugin-security): record the new test's engine doubles and refuse combinators in its fake matcher
`check:engine-double-contract` RETAINED the two engine doubles the new suite
pins, so the ledger learns about them or it never protects the file.
`check:where-matcher` flagged the synthetic driver's permission-set matcher as
combinator-blind: it now refuses a `$`-prefixed key inside the matcher itself
rather than one frame out, so a double that does not implement `$or` says so
instead of reporting a row it never understood as absent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 3c557e2 commit 1c83ca2
4 files changed
Lines changed: 760 additions & 18 deletions
File tree
- .changeset
- packages/plugins/plugin-security/src
- 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 | + | |
0 commit comments