fix(security): restrict service monitors to probe tasks

This commit is contained in:
naiba
2026-08-15 05:00:28 +00:00
parent 42d9e4c8c3
commit 38824dbc11
16 changed files with 338 additions and 20 deletions
@@ -16,6 +16,10 @@
repository: "https://github.com/karllao/nezha-pixel"
author: "karllao"
version: "v1.6.0"
# Third-party user themes consume the opaque Server.PublicNote field. Theme
# maintainers must validate URL schemes (including after decoding) before using
# values such as customData.orderLink in href or window.open; the Dashboard
# backend and admin frontend do not execute those fields.
- path: "nazhua-dist"
name: "Nazhua"
repository: "https://github.com/hi2shark/nazhua"
@@ -0,0 +1,37 @@
package singleton
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/nezhahq/nezha/model"
)
func TestServiceSentinelUpdateRejectsNonProbeTaskTypes(t *testing.T) {
ss := &ServiceSentinel{}
require.Error(t, ss.Update(nil))
for _, taskType := range []uint8{0, model.TaskTypeCommand, model.TaskTypeApplyConfig, model.TaskTypeExec, 255} {
require.Error(t, ss.Update(&model.Service{Type: taskType}), "type %d must not be scheduled", taskType)
}
}
func TestServiceSentinelQuarantinesInvalidPersistedTypes(t *testing.T) {
ss := newServiceMonitorSecurityHarness(t)
insert := `INSERT INTO services
(id, user_id, name, type, target, duration, cover, skip_servers_raw, fail_trigger_tasks_raw, recover_trigger_tasks_raw)
VALUES (?, 100, ?, ?, 'example.invalid:443', 3600, ?, '{}', '[]', '[]')`
require.NoError(t, DB.Exec(insert, 91, "legacy-command", model.TaskTypeCommand, model.ServiceCoverIgnoreAll).Error)
require.NoError(t, DB.Exec(insert, 92, "legacy-apply-config", model.TaskTypeApplyConfig, model.ServiceCoverIgnoreAll).Error)
require.NoError(t, DB.Exec(insert, 93, "valid-probe", model.TaskTypeTCPPing, model.ServiceCoverIgnoreAll).Error)
require.NoError(t, ss.loadServiceHistory())
_, commandLoaded := ss.Get(91)
_, applyConfigLoaded := ss.Get(92)
valid, validLoaded := ss.Get(93)
require.False(t, commandLoaded)
require.False(t, applyConfigLoaded)
require.True(t, validLoaded)
require.Equal(t, uint8(model.TaskTypeTCPPing), valid.Type)
}
+17
View File
@@ -209,7 +209,15 @@ func (ss *ServiceSentinel) loadServiceHistory() error {
return err
}
validServices := services[:0]
for _, service := range services {
if err := model.ValidateServiceMonitorType(uint64(service.Type)); err != nil {
// Existing databases may contain values written before Service.Type was
// constrained. Quarantine them in the database for operator review, but
// never register a cron job that could dispatch a privileged Agent task.
log.Printf("NEZHA>> quarantining service %d: %v", service.ID, err)
continue
}
task := service
// 通过cron定时将服务监控任务传递给任务调度管道
service.CronJobID, err = CronShared.AddFunc(task.CronSpec(), func() {
@@ -222,7 +230,9 @@ func (ss *ServiceSentinel) loadServiceHistory() error {
ss.serviceCurrentStatusData[service.ID] = new(serviceTaskStatus)
ss.serviceCurrentStatusData[service.ID].result = make([]*pb.TaskResult, 0, _CurrentStatusSize)
ss.serviceStatusToday[service.ID] = &_TodayStatsOfService{}
validServices = append(validServices, service)
}
services = validServices
ss.serviceList = services
sortServices(ss.serviceList)
@@ -339,6 +349,13 @@ func (ss *ServiceSentinel) loadTodayStats(today time.Time) {
}
func (ss *ServiceSentinel) Update(m *model.Service) error {
if m == nil {
return fmt.Errorf("service is nil")
}
if err := model.ValidateServiceMonitorType(uint64(m.Type)); err != nil {
return err
}
ss.serviceResponseDataStoreLock.Lock()
defer ss.serviceResponseDataStoreLock.Unlock()
ss.monthlyStatusLock.Lock()
+4 -1
View File
@@ -23,7 +23,10 @@ func initUser() {
var users []model.User
DB.Find(&users)
// for backward compatibility
// Backward compatibility for pre-user-scoped Agents. AgentSecretKey is a
// deployment-wide migration/master credential, so user 0 is intentionally
// not tenant-scoped. Do not remove this mapping until every legacy Agent has
// rotated to a per-user/per-Agent credential; doing so would disconnect them.
UserInfoMap[0] = model.UserInfo{
Role: model.RoleAdmin,
AgentSecret: Conf.AgentSecretKey,