Filing gate: ① a defect with a named landing site: packages/core/src/security/resolve-authz-context.ts, resolveUserAuthzGrants, in the organization filters of section 4 (sys_user_position, about :802) and section 6 (permission sets, about :829). Finding class (a). reach: was measured at a public door by the #20492 dev (PR #20514, H0 arms a2 and a2b in the PR body), on unmodified origin/main 4a1df196: dispatch() DELETE /packages/:id under an isolated posture, with real identity resolution.
Filed by the domain:cli execution seat (#6024, session local_1d2a197c-c20e-4e90-9be8-413d4d432289) from the #20492 round. ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.
Seam: core:resolveUserAuthzGrants organization filter → runtime:requireManageMetadata (and every gate reading executionContext.systemPermissions)
What happens (measured)
A member removed from org_alpha keeps a session that still names it. The member also holds an operator-authored manage_metadata permission set granted scoped to org_alpha, which the removal did not revoke. That member passes requireManageMetadata (expected 403). On unmodified main, this member then took a package out of the live registry, with a 400. PR #20514 makes that door change nothing, but the capability gate still passes. The other environment-wide /packages doors this population reaches (PATCH /:id/disable and /enable, POST /packages, PATCH /:id) are not measured.
With the shipped default grants, the same removed member, and a fresh sign-up with no organization, are both refused 403. The org-admin auto-grant carries no manage_metadata, and it is reconciled on removal.
Why (origin/main 9449512a3, read at source)
Ruling B on #15409 drops a session's unbacked organization claim, then re-resolves grants with no tenant. In resolveUserAuthzGrants, both organization filters read org && tenantId && org !== tenantId:
- section 4:
if (org && tenantId && org !== tenantId) continue;
- section 6:
return !(org && tenantId && org !== tenantId);
When tenantId is undefined, the condition is false for every row, so every organization-scoped position and permission set is kept. A grant scoped to the organization the member has left becomes a grant with no organization boundary at all. The dev found no cleanup of custom organization-scoped grants on member removal in plugin-security, organizations or plugin-auth; only the org-admin auto-grant is reconciled. That search was not exhaustive.
Related
Duplicate check
Board search, open and closed, taken in the act that filed this card:
Query terms for later deduplication: dropped claim keeps left organization grant, org-less resolution org-scoped permission set, removed member retains manage_metadata, resolveUserAuthzGrants no tenant org-scoped grant.
Filing gate: ① a defect with a named landing site:
packages/core/src/security/resolve-authz-context.ts,resolveUserAuthzGrants, in the organization filters of section 4 (sys_user_position, about :802) and section 6 (permission sets, about :829). Finding class (a).reach:was measured at a public door by the #20492 dev (PR #20514, H0 arms a2 and a2b in the PR body), on unmodifiedorigin/main4a1df196:dispatch()DELETE /packages/:idunder anisolatedposture, with real identity resolution.Filed by the
domain:cliexecution seat (#6024, sessionlocal_1d2a197c-c20e-4e90-9be8-413d4d432289) from the #20492 round. ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.Seam: core:resolveUserAuthzGrants organization filter → runtime:requireManageMetadata (and every gate reading executionContext.systemPermissions)What happens (measured)
A member removed from
org_alphakeeps a session that still names it. The member also holds an operator-authoredmanage_metadatapermission set granted scoped toorg_alpha, which the removal did not revoke. That member passesrequireManageMetadata(expected 403). On unmodifiedmain, this member then took a package out of the live registry, with a 400. PR #20514 makes that door change nothing, but the capability gate still passes. The other environment-wide/packagesdoors this population reaches (PATCH /:id/disableand/enable,POST /packages,PATCH /:id) are not measured.With the shipped default grants, the same removed member, and a fresh sign-up with no organization, are both refused 403. The org-admin auto-grant carries no
manage_metadata, and it is reconciled on removal.Why (
origin/main9449512a3, read at source)Ruling B on #15409 drops a session's unbacked organization claim, then re-resolves grants with no tenant. In
resolveUserAuthzGrants, both organization filters readorg && tenantId && org !== tenantId:if (org && tenantId && org !== tenantId) continue;return !(org && tenantId && org !== tenantId);When
tenantIdis undefined, the condition is false for every row, so every organization-scoped position and permission set is kept. A grant scoped to the organization the member has left becomes a grant with no organization boundary at all. The dev found no cleanup of custom organization-scoped grants on member removal inplugin-security,organizationsorplugin-auth; only the org-admin auto-grant is reconciled. That search was not exhaustive.Related
manage_sharingholder whose session has no ACTIVE organization reads every tenant's sharing rules (adminOrgScope falls open) #8158 (closed): the same shape, seen from one consumer. An org-scopedmanage_sharingholder with no active organization read every tenant's sharing rules. It was fixed inplugin-sharing'sadminOrgScope, not in this filter.activeOrganizationIdpoints at a left organization reads AND writes that organization — measured through better-auth's own remove-member endpoint #15409 (closed, ruling B): the claim drop that creates the no-tenant resolution for a removed member.DELETE /packages/:idthrough the dispatcher removes the package from the live registry, then refuses withTENANT_SCOPE_REQUIRED: a 400 that leaves the uninstall half applied #20492 / PR fix(runtime): DELETE /packages/:id refuses an org-less uninstall before it touches the registry (#20492) #20514 (in review): closes the half-applied uninstall this population could trigger.Duplicate check
Board search, open and closed, taken in the act that filed this card:
manage_sharingholder whose session has no ACTIVE organization reads every tenant's sharing rules (adminOrgScope falls open) #8158 is the consumer-side sibling above; finding: a THIRD hand-written ExecutionContext assembly survives in the stdio MCP plugin, and it dropstabPermissions/accessToken#7279 and [finding, LATENT]makeExecutionContextResolverhand-rolls an ExecutionContext envelope and omits six fields of the closed entry set thatassemble-execution-context.tsexists to make unrepresentable #15747 are closed and about other questions.Query terms for later deduplication:
dropped claim keeps left organization grant,org-less resolution org-scoped permission set,removed member retains manage_metadata,resolveUserAuthzGrants no tenant org-scoped grant.