Problem
Security-relevant inconsistency, not just a missing feature. GET /v1/app/digest's handler (src/api/routes.ts ~line 2166) gates on requireAppRole(c, ["maintainer","owner","operator"]) — the same non-operator roles a hosted tenant's own maintainer legitimately holds — then calls listRepositories(c.env)/listInstallationHealth(c.env) completely unfiltered into buildDigestItems, with no per-tenant scoping applied. Its sibling GET /v1/app/maintainer-dashboard (~lines 1596-1619) already solves this exact problem: for any non-operator session it calls loadControlPanelRoleSummary/tenant-scoping helpers before building the response. /v1/app/digest doesn't call the equivalent path.
Today this has no real victim (only one real tenant — JSONbored's own instance — exists in production), but it's a live gap in already-deployed code that becomes an actual cross-tenant data leak the moment a second real hosted tenant exists — worth fixing now, while it's cheap and there's no real exposure yet, not after.
Area
src/api/routes.ts, src/services/control-panel-roles.ts.
Proposal
Apply the same tenant-scoping /v1/app/maintainer-dashboard already implements to /v1/app/digest — reuse its exact pattern (loadControlPanelRoleSummary or whatever helper it calls), don't invent a second scoping mechanism.
Deliverables
Resources
src/api/routes.ts (~1596-1619 for the correct pattern, ~2166 for the gap)
src/services/control-panel-roles.ts
apps/loopover-ui/src/components/site/app-panels/digest-panel.tsx (the consumer)
Boundaries
Scoping fix only — no other behavior change to the digest endpoint.
maintainer-only — access-control fix, needs maintainer review given the data-exposure stakes, not a first-pass contributor task.
Problem
Security-relevant inconsistency, not just a missing feature.
GET /v1/app/digest's handler (src/api/routes.ts~line 2166) gates onrequireAppRole(c, ["maintainer","owner","operator"])— the same non-operator roles a hosted tenant's own maintainer legitimately holds — then callslistRepositories(c.env)/listInstallationHealth(c.env)completely unfiltered intobuildDigestItems, with no per-tenant scoping applied. Its siblingGET /v1/app/maintainer-dashboard(~lines 1596-1619) already solves this exact problem: for any non-operator session it callsloadControlPanelRoleSummary/tenant-scoping helpers before building the response./v1/app/digestdoesn't call the equivalent path.Today this has no real victim (only one real tenant — JSONbored's own instance — exists in production), but it's a live gap in already-deployed code that becomes an actual cross-tenant data leak the moment a second real hosted tenant exists — worth fixing now, while it's cheap and there's no real exposure yet, not after.
Area
src/api/routes.ts,src/services/control-panel-roles.ts.Proposal
Apply the same tenant-scoping
/v1/app/maintainer-dashboardalready implements to/v1/app/digest— reuse its exact pattern (loadControlPanelRoleSummaryor whatever helper it calls), don't invent a second scoping mechanism.Deliverables
/v1/app/digestscoped identically to/v1/app/maintainer-dashboardfor any non-operator session.Resources
src/api/routes.ts(~1596-1619 for the correct pattern, ~2166 for the gap)src/services/control-panel-roles.tsapps/loopover-ui/src/components/site/app-panels/digest-panel.tsx(the consumer)Boundaries
Scoping fix only — no other behavior change to the digest endpoint.
maintainer-only — access-control fix, needs maintainer review given the data-exposure stakes, not a first-pass contributor task.