fix(service): hide EnableShowInService=false services from sideband endpoints

GHSA-vrmh-5mmx-hjwx: GET /api/v1/server/:id/service and
GET /api/v1/service/:id/history both iterate the raw service list and
emit ServiceName / timing for any service that happens to monitor the
queried server, ignoring the owner's EnableShowInService=false flag.
Both routes are on optionalAuth, so unauthenticated visitors could
enumerate hidden services by name and timing.

Introduce userCanViewService(c, service):
- EnableShowInService=true     -> always visible
- admin                        -> always visible
- authenticated owner          -> visible (HasPermission)
- everyone else                -> hidden

getServiceHistory rejects unknown-or-invisible service with the same
'service not found' message so the endpoint cannot be used as an
oracle. listServerServices pre-filters the sorted service list.

Owners and admins keep their existing visibility into their own hidden
services.

Tests:
- TestUserCanViewServiceVisibleServiceIsPublic
- TestUserCanViewServiceHiddenServiceRejectsGuest
- TestUserCanViewServiceHiddenServiceRejectsForeignMember
- TestUserCanViewServiceHiddenServiceAllowsOwner
- TestUserCanViewServiceHiddenServiceAllowsAdmin

Co-authored-by: cloudcode <cloudcode@users.noreply.github.com>
This commit is contained in:
naiba
2026-05-26 03:59:59 +00:00
co-authored by cloudcode
parent 9929e04643
commit 94b7deef24
3 changed files with 72 additions and 3 deletions
+8 -3
View File
@@ -130,9 +130,8 @@ func getServiceHistory(c *gin.Context) (*model.ServiceHistoryResponse, error) {
return nil, err
}
// 检查服务是否存在
service, ok := singleton.ServiceSentinelShared.Get(serviceID)
if !ok || service == nil {
if !ok || service == nil || !userCanViewService(c, service) {
return nil, singleton.Localizer.ErrorT("service not found")
}
@@ -285,7 +284,13 @@ func listServerServices(c *gin.Context) ([]*model.ServiceInfos, error) {
return nil, singleton.Localizer.ErrorT("unauthorized: only 1d data available for guests")
}
services := singleton.ServiceSentinelShared.GetSortedList()
allServices := singleton.ServiceSentinelShared.GetSortedList()
services := make([]*model.Service, 0, len(allServices))
for _, s := range allServices {
if userCanViewService(c, s) {
services = append(services, s)
}
}
var result []*model.ServiceInfos