feat(api): scope installation health/repair to the owning tenant (#7661) - #8076
Closed
boskodev790 wants to merge 1 commit into
Closed
feat(api): scope installation health/repair to the owning tenant (#7661)#8076boskodev790 wants to merge 1 commit into
boskodev790 wants to merge 1 commit into
Conversation
…Nbored#7661) The /v1/installations list, per-installation health, repair, and repair/refresh routes had no inline ownership check — they were operator-only only because the coarse canSessionAccessPath gate fell through to the global operator check. Add a tenant-scoped equivalent so a hosted tenant can self-serve their own installation: allow those paths through canSessionAccessPath, then in each handler reuse the maintainer-dashboard's identity -> role-gate -> loadControlPanelAccessScope pattern. A non-operator session is scoped to its own installations (list filtered; another tenant's id returns the same not-found shape so existence never leaks; refresh is scope-checked before any mutation); an operator or server-to-server token keeps the unscoped fleet view unchanged. Closes JSONbored#7661
Contributor
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8076 +/- ##
==========================================
+ Coverage 91.83% 95.30% +3.47%
==========================================
Files 734 1 -733
Lines 75086 2555 -72531
Branches 23068 837 -22231
==========================================
- Hits 68952 2435 -66517
+ Misses 5034 27 -5007
+ Partials 1100 93 -1007
Flags with carried forward coverage won't be shown. Click here to find out more.
|
This was referenced Jul 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The installation self-service routes —
GET /v1/installations,GET /v1/installations/:id/health,GET /v1/installations/:id/repair, andPOST /v1/installations/:id/repair/refresh— carried no inlinerole/ownership check. They were reachable only because the coarse
canSessionAccessPathgate fell throughto the global operator check, so today only the fleet operator can see or repair installation health; a
hosted tenant has no self-service equivalent for their own installation (#7661).
This adds a tenant-scoped equivalent by reusing the maintainer-dashboard's exact scoping pattern:
canSessionAccessPathnow lets these four paths through for a session, with per-installation authorityenforced in the handlers (mirroring how the repo-scoped routes are allowed through the coarse gate and
then check authority themselves).
resolveInstallationSelfServiceScope, which authenticates, role-gates(
maintainer/owner/operator), then computesidentity.kind === "session" && !operator ? loadControlPanelAccessScope(...) : null— the identical identity → role-gate → scope shape/v1/app/maintainer-dashboarduses.scope.installationIds(the authoritativeper-tenant set from the same
loadControlPanelAccessScope): the list is filtered; a per-installationread for another tenant returns the same
installation_health_not_foundshape as a genuinely-missingid, so cross-tenant existence never leaks; and
repair/refreshis scope-checked before the mutation,so a tenant can never trigger a refresh on another tenant's installation.
scope === null— the unscoped fleet view, byte-for-byteunchanged from today (every existing operator/service-token test still passes).
Out of scope (per the issue): bulk/installation-level pause-all controls (#7676).
Scope
type(scope): short summaryConventional Commit format.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Validation
Verified locally against the current
mainbase (deps refreshed withnpm ci;@loopover/enginerebuiltbefore typecheck, exactly as
test:cidoes):git diff --checknpm run typecheck— 0 errors.npm run test:coverage— the newtest/unit/routes-installation-self-service.test.tspasses (9cases), and every changed line and branch in
src/api/routes.tsis covered (100% on the diff,verified via lcov). Only
src/**is Codecov-measured; this change issrc/api/routes.tsonly.npm run ui:openapi:check— no spec drift (routes' request/response shapes are unchanged; only theauth/scoping behavior is added).
routes-errors.test.ts,api.test.ts,auth.test.ts) still pass— operator/service-token behavior is unchanged.
403 on every route; operator/service token → unscoped list + any installation; tenant → own-only list;
tenant reads/repairs its own installation (200) but another tenant's returns 404; tenant's cross-tenant
refresh is denied 404 without invoking the refresh mutation; tenant refresh of its own installation
succeeds.
Safety
rankings, or private maintainer evidence are exposed.
cross-tenant access (404 without leaking existence, and without mutating) are all covered. The scoping
reuses the already-reviewed
loadControlPanelAccessScopepath rather than inventing a new one.ui:openapi:checkclean).UI Evidence
N/A — this PR adds no visible UI, frontend page, or component. It is backend route authorization/scoping in
src/api/routes.tsplus its tests.Notes
installation_health_not_found404 as a missing id(rather than a 403), so a tenant cannot probe which installation ids exist for other tenants — the same
no-leak posture the issue calls out.
repair/refreshis scope-checked beforerefreshInstallationHealthForInstallationruns, so across-tenant refresh performs zero work (asserted by a test that also verifies no
fetchoccurs).