diff --git a/internal/api/config_setup_handlers.go b/internal/api/config_setup_handlers.go index c8c9b6ae0..3e41a7726 100644 --- a/internal/api/config_setup_handlers.go +++ b/internal/api/config_setup_handlers.go @@ -718,12 +718,19 @@ func (h *ConfigHandlers) handleAutoRegister(w http.ResponseWriter, r *http.Reque record, ok := cfg.ValidateAPIToken(apiToken) config.Mu.Unlock() if ok && record != nil && record.HasScope(config.ScopeAgentReport) { - authenticated = true - r = r.WithContext(context.WithValue(r.Context(), agentAutoRegContextKey{}, true)) - log.Info(). - Str("type", req.Type). - Str("host", req.Host). - Msg("Auto-register authenticated via agent API token (update-only mode)") + // Reject cross-org tokens: if the request context has an explicit + // non-default org, the token must belong to that same org. + requestOrgID := GetOrgID(r.Context()) + orgMismatch := requestOrgID != "" && requestOrgID != "default" && + record.OrgID != "" && record.OrgID != requestOrgID + if !orgMismatch { + authenticated = true + r = r.WithContext(context.WithValue(r.Context(), agentAutoRegContextKey{}, true)) + log.Info(). + Str("type", req.Type). + Str("host", req.Host). + Msg("Auto-register authenticated via agent API token (update-only mode)") + } } } } diff --git a/internal/api/security_regression_test.go b/internal/api/security_regression_test.go index 34526b04a..31e8e54ad 100644 --- a/internal/api/security_regression_test.go +++ b/internal/api/security_regression_test.go @@ -2065,16 +2065,19 @@ func TestAutoRegisterRejectsAgentTokenWithoutSetupToken(t *testing.T) { router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0") router.configHandlers.SetConfig(cfg) - body := `{"type":"pve","host":"https://192.168.1.1:8006","tokenId":"test@pam!pulse","tokenValue":"secret"}` + // Agent-report tokens can authenticate auto-register but are restricted to + // updating existing nodes. Attempting to register a brand-new node must + // return 403, not succeed. + body := `{"type":"pve","host":"https://192.168.1.1:8006","tokenId":"pulse-monitor@pve!pulse-192-168-1-1","tokenValue":"secret","source":"agent","serverName":"newhost"}` req := httptest.NewRequest(http.MethodPost, "/api/auto-register", strings.NewReader(body)) req.Header.Set("X-API-Token", rawToken) rec := httptest.NewRecorder() router.Handler().ServeHTTP(rec, req) - if rec.Code != http.StatusUnauthorized { - t.Fatalf("expected 401 when agent API token is provided without a Pulse setup token, got %d", rec.Code) + if rec.Code != http.StatusForbidden { + t.Fatalf("expected 403 when agent API token is used to register a new node, got %d", rec.Code) } - if !strings.Contains(rec.Body.String(), "Pulse setup token required") { - t.Fatalf("expected setup-token requirement guidance, got %q", rec.Body.String()) + if !strings.Contains(rec.Body.String(), "agent token auth permits token updates for existing nodes only") { + t.Fatalf("expected agent-token restriction message, got %q", rec.Body.String()) } }