Files
nezha_domains/cmd/dashboard/controller/permissions.go
T
naibaandcloudcode a8f014838c 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>
2026-05-26 03:59:59 +00:00

68 lines
1.4 KiB
Go

package controller
import (
"github.com/gin-gonic/gin"
"github.com/nezhahq/nezha/model"
"github.com/nezhahq/nezha/service/singleton"
)
func callerIsAdmin(c *gin.Context) bool {
auth, ok := c.Get(model.CtxKeyAuthorizedUser)
if !ok {
return false
}
user, ok := auth.(*model.User)
if !ok || user == nil {
return false
}
return user.Role.IsAdmin()
}
func userCanViewServer(c *gin.Context, server *model.Server) bool {
if server == nil {
return false
}
if callerIsAdmin(c) {
return true
}
if _, isMember := c.Get(model.CtxKeyAuthorizedUser); isMember {
if server.HasPermission(c) {
return true
}
return !server.HideForGuest
}
return !server.HideForGuest
}
func userCanViewService(c *gin.Context, service *model.Service) bool {
if service == nil {
return false
}
if service.EnableShowInService {
return true
}
if callerIsAdmin(c) {
return true
}
if _, isMember := c.Get(model.CtxKeyAuthorizedUser); isMember {
return service.HasPermission(c)
}
return false
}
func assertOwnsNotificationGroup(c *gin.Context, groupID uint64) error {
if groupID == 0 {
return nil
}
var ng model.NotificationGroup
if err := singleton.DB.First(&ng, groupID).Error; err != nil {
return singleton.Localizer.ErrorT("notification group id %d does not exist", groupID)
}
if !ng.HasPermission(c) {
return singleton.Localizer.ErrorT("permission denied")
}
return nil
}