Provenance/Integrity preservation in provisioning API (authorization/integrity safeguard)
Description
Security hardening: The change enforces immutability of alert provenance for existing rules during provisioning updates. If a stored provenance exists and a provisioning request attempts to change it, the API now returns a structured provenance mismatch error (using errProvenanceMismatch) with a 409 Conflict semantics. This prevents provenance metadata tampering and strengthens integrity/authentication guarantees around alert rule provisioning.
Commit Details
Author: Khalil Haji
Date: 2026-05-06 19:45 UTC
Message:
Alerting: return 409 when changing provenance isn't allowed in provisioning APIs (#124178)
* Alerting: return 409 when changing provenance isn't allowed
* update tests
Triage Assessment
Vulnerability Type: Authentication/Authorization bypass
Confidence: HIGH
Reasoning:
The commit enforces a check that prevents changing the provenance of an alert rule during provisioning updates and returns a 409 with a structured error when a mismatch is detected. This prevents tampering with provenance metadata, which is an integrity/authentication-related safeguard and reduces the risk of unauthorized rule modification.
Verification Assessment
Vulnerability Type: Provenance/Integrity preservation in provisioning API (authorization/integrity safeguard)
Confidence: HIGH
Affected Versions: Grafana 12.x prior to 12.4.0 (provisioning of ngalert alert rules).
Code Diff
diff --git a/pkg/services/ngalert/provisioning/alert_rules.go b/pkg/services/ngalert/provisioning/alert_rules.go
index 2a394eaf1ce43..51a261ec53ec8 100644
--- a/pkg/services/ngalert/provisioning/alert_rules.go
+++ b/pkg/services/ngalert/provisioning/alert_rules.go
@@ -820,7 +820,13 @@ func (service *AlertRuleService) UpdateAlertRule(ctx context.Context, user ident
return models.AlertRule{}, err
}
if storedProvenance != provenance && storedProvenance != models.ProvenanceNone {
- return models.AlertRule{}, fmt.Errorf("cannot change provenance from '%s' to '%s'", storedProvenance, provenance)
+ return models.AlertRule{}, errProvenanceMismatch.Build(errutil.TemplateData{
+ Public: map[string]interface{}{
+ "ProvidedProvenance": provenance,
+ "StoredProvenance": storedProvenance,
+ "Operation": "update",
+ },
+ })
}
if rule.NotificationSettings != nil {
validator, err := service.nsValidatorProvider.Validator(ctx, rule.OrgID)
diff --git a/pkg/services/ngalert/provisioning/alert_rules_test.go b/pkg/services/ngalert/provisioning/alert_rules_test.go
index 359c2f04e47ae..9d4be8ba4fd5c 100644
--- a/pkg/services/ngalert/provisioning/alert_rules_test.go
+++ b/pkg/services/ngalert/provisioning/alert_rules_test.go
@@ -626,6 +626,7 @@ func TestIntegrationAlertRuleService(t *testing.T) {
require.NoError(t, err)
} else {
require.Error(t, err)
+ require.True(t, errProvenanceMismatch.Base.Is(err))
}
})
}