Commit 77e19a2
fix(service-datasource): the admin routes derive the tenancy posture — an ex-member's org-stamped API key is no longer admitted (#16011)
* fix(service-datasource): derive the tenancy posture at the admin-routes admission seam
The datasource ADMIN routes accept real request headers, so `x-api-key` is
admitted there — but `requireDatasourceAdmin` called `resolveAuthzContext`
with no `tenancyPosture`, and both posture-conditional API-key refusals are
gated on the caller supplying one. Neither ran, so under a wall-enforcing
posture an API key stamped with an organization its owner has left was
admitted and the family then gated it on `authz.systemPermissions` alone.
The posture is classified per #13906 decision 1 option A: a `tenancy` service
that was NEVER REGISTERED is branded and resolves quietly to "no posture"; one
that was registered and FAILED to build raises `AuthzStoreUnavailableError`,
which `requireDatasourceAdmin`'s existing catch re-raises rather than
laundering into a denial (#13279).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
* test(service-datasource): the admin-routes API-key admission matrix, its permanent wiring ablation and the two option-A arms
29 cases on one registrar: controls in both directions (a current member reads
and writes, an anonymous caller is refused, an entitled-less key gets the
capability refusal), the ex-member and organization-less subject rows under
`isolated`, and a permanently-held ablation whose handle is the wiring this
package shipped before this card — a PluginContext with no `getKernel`.
Writes are read back FROM THE DATASOURCE STORE, never from the response body.
`group` is MEASURED rather than assumed: the ex-member is refused there too
(`postureEnforcesWall('group')`), while the organization-less key is admitted
(union scope).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
* chore(changeset): @objectstack/service-datasource patch; pin the outage class rather than its digits
Sized as #15365 was: a fail-closed bug fix, no accept set widens.
The outage arm now asserts the outage CLASS. Measured on this tree, the family
answers 500 INTERNAL_ERROR rather than the 503 SERVICE_UNAVAILABLE the brand
carries — a pre-existing relay gap this card did not open (the same escape has
existed for the `ql` permission-store outage since #13279) and does not repair.
Asserting the class keeps the pin on the security property.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
* test(service-datasource): type the harness app off the adapter's own accessor
`tsc --noEmit` reds on a hand-written `{ fetch: (req: Request) => Promise<Response> }`:
Hono's `fetch` takes an env and an execution context after the request, so the
narrower signature is a type error rather than a simplification.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent c91feb2 commit 77e19a2
3 files changed
Lines changed: 733 additions & 1 deletion
File tree
- .changeset
- packages/services/service-datasource/src
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
0 commit comments