Censused under #15256 (maintainer ruling 2026-09-04, item 5). That card repaired the REST single-kernel seam; this is the same hole through another door.
This site
packages/plugins/plugin-sharing/src/sharing-plugin.ts:847
const authz = await resolveAuthzContext({ ql, headers, getSession });
Real request headers, so x-api-key is accepted (readHeader(headers, 'x-api-key'), api-key.ts). No posture.
⚠️ Worth noting when this is triaged: this plugin ALREADY resolves a tenancy probe a few hundred lines up (ctx.getService('tenancy') behind SharingTenancyProbe, ADR-0105 D1 late binding) for its own wall reconciliation. So this site may be the cheapest of the six — but the probe's shape and the classification decision-1-option-A requires still have to be reconciled, which is why it is filed rather than patched blind.
The mechanism, unchanged from #15256
resolveAuthzContext gates BOTH posture-conditional API-key refusals on a tenancyPosture its caller supplies:
organization_required — packages/core/src/security/api-key.ts, if (!tenantId && tenancyPosture)
organization_membership_ended — packages/core/src/security/resolve-authz-context.ts, if (keyPrincipal?.tenantId && input.tenancyPosture)
No posture supplied means neither guard runs. The key's tenantId is sys_api_key.active_organization_id copied verbatim — the caller's own stored claim, never vetted against current membership. So under a wall-enforcing posture (isolated), an API key stamped with an organization its owner has left is admitted, carrying that organization as its tenant.
The census this came from
Of the eight non-test resolveAuthzContext callers on main, exactly two supply a posture:
The other six do not. This issue is one of them; the ruling directed one issue per site.
Why #15256's PR did not fix it inline
Ruling item 5 says fix in that PR if it is one line per site. It is not. A correct derivation has to carry decision-1-option-A's classification (#13906): a tenancy service that was never registered is branded and resolves quietly to "no posture", while one that was registered and FAILED to build must raise AuthzStoreUnavailableError (503). A naive try { ... } catch { undefined } at this seam re-introduces precisely the permissive-on-failure defect #13906 was filed to repair — a failure reading as "this check does not apply".
Refs: #15256 (the ruling and the repaired REST seam) · #15163 (the framework measurement) · #13906 (decision 1 option A) · ADR-0105 D2/D3 · ADR-0123 D2.
Blocked-by: #15256
Censused under #15256 (maintainer ruling 2026-09-04, item 5). That card repaired the REST single-kernel seam; this is the same hole through another door.
This site
packages/plugins/plugin-sharing/src/sharing-plugin.ts:847Real request headers, so
x-api-keyis accepted (readHeader(headers, 'x-api-key'),api-key.ts). No posture.ctx.getService('tenancy')behindSharingTenancyProbe, ADR-0105 D1 late binding) for its own wall reconciliation. So this site may be the cheapest of the six — but the probe's shape and the classification decision-1-option-A requires still have to be reconciled, which is why it is filed rather than patched blind.The mechanism, unchanged from #15256
resolveAuthzContextgates BOTH posture-conditional API-key refusals on atenancyPostureits caller supplies:organization_required—packages/core/src/security/api-key.ts,if (!tenantId && tenancyPosture)organization_membership_ended—packages/core/src/security/resolve-authz-context.ts,if (keyPrincipal?.tenantId && input.tenancyPosture)No posture supplied means neither guard runs. The key's
tenantIdissys_api_key.active_organization_idcopied verbatim — the caller's own stored claim, never vetted against current membership. So under a wall-enforcing posture (isolated), an API key stamped with an organization its owner has left is admitted, carrying that organization as its tenant.The census this came from
Of the eight non-test
resolveAuthzContextcallers onmain, exactly two supply a posture:packages/rest/src/rest-server.ts— both wirings, after [decision · p0] an ex-member API key reads AND writes another organization's rows on the single-kernel wiring underisolated— the wall compares against the caller's own unvetted claim #15256packages/runtime/src/security/resolve-execution-context.ts— already didThe other six do not. This issue is one of them; the ruling directed one issue per site.
Why #15256's PR did not fix it inline
Ruling item 5 says fix in that PR if it is one line per site. It is not. A correct derivation has to carry decision-1-option-A's classification (#13906): a
tenancyservice that was never registered is branded and resolves quietly to "no posture", while one that was registered and FAILED to build must raiseAuthzStoreUnavailableError(503). A naivetry { ... } catch { undefined }at this seam re-introduces precisely the permissive-on-failure defect #13906 was filed to repair — a failure reading as "this check does not apply".Refs: #15256 (the ruling and the repaired REST seam) · #15163 (the framework measurement) · #13906 (decision 1 option A) · ADR-0105 D2/D3 · ADR-0123 D2.
Blocked-by: #15256