CVE-2025-3415
Description
Grafana is an open-source platform for monitoring and observability. The Grafana Alerting DingDing integration was not properly protected and could be exposed to users with Viewer permission. Fixed in versions 10.4.19+security-01, 11.2.10+security-01, 11.3.7+security-01, 11.4.5+security-01, 11.5.5+security-01, 11.6.2+security-01 and 12.0.1+security-01
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Grafana Alerting DingDing integration exposes webhook URL to users with Viewer permission due to missing secret field handling.
Vulnerability
The Grafana Alerting DingDing integration did not properly protect the webhook URL configured for that channel. The vulnerability stems from the fact that the integration’s URL field was not declared as a secure setting. As a result, a Grafana user with only Viewer permissions — who would normally have read-only access to alerting configurations — could retrieve the DingDing webhook URL through the Alerting API or UI. The affected code path lacked the necessary patchNewSecureFields logic that decrypts and migrates the URL from SecureSettings into Settings only when needed, a fix introduced in the referenced commits [2][3][4].
Exploitation
To exploit this issue, an attacker must be authenticated to a Grafana instance and have the Viewer role (the lowest built-in non-anonymous role). No additional privileges are required beyond those available to a Viewer. The attacker can then navigate to the Alerting configuration for a DingDing contact point or query the relevant API endpoint to obtain the plaintext URL. The attack is low complexity and requires no special network position beyond normal Grafana access. The vulnerability exists across multiple Grafana versions prior to the fixed releases listed in the advisory [1].
Impact
An attacker who obtains the DingDing webhook URL can send arbitrary messages to that DingTalk chat group, impersonating the Grafana alert notification. This could lead to social engineering, distribution of malicious links, or other abuse within the communication platform. The integrity and reliability of automated alert notifications are compromised. The confidentiality of the webhook URL is breached, which is treated as a secret token.
Mitigation
Grafana has released patches for all supported versions: 10.4.19+security-01, 11.2.10+security-01, 11.3.7+security-01, 11.4.5+security-01, 11.5.5+security-01, 11.6.2+security-01, and 12.0.1+security-01 [1]. Organizations running affected versions should upgrade immediately. There is no known workaround; upgrading is the only remediating action. The fix ensures the DingDing URL is handled as a secure field and not exposed to low-privilege users.
AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/grafana/grafanaGo | < 1.9.2-0.20250514160932-04111e9f2afd | 1.9.2-0.20250514160932-04111e9f2afd |
Affected products
2< 1.9.2-0.20250514160932-04111e9f2afd+ 1 more
- (no CPE)range: < 1.9.2-0.20250514160932-04111e9f2afd
- (no CPE)range: >=10.4.19+security-01, <11.2.10+security-01, >=11.2.10+security-01, <11.3.7+security-01, >=11.3.7+security-01, <11.4.5+security-01, >=11.4.5+security-01, <11.5.5+security-01, >=11.5.5+security-01, <11.6.2+security-01, >=11.6.2+security-01, <12.0.1+security-01
Patches
8910eb1dd9e61Security: apply patch 428 (#106710)
5 files changed · +66 −8
pkg/services/ngalert/notifier/alertmanager.go+55 −1 modified@@ -53,6 +53,7 @@ type alertmanager struct { Store AlertingStore stateStore stateStore DefaultConfiguration string + decryptFn alertingNotify.GetDecryptedValueFn } // maintenanceOptions represent the options for components that need maintenance on a frequency within the Alertmanager. @@ -149,6 +150,7 @@ func NewAlertmanager(ctx context.Context, orgID int64, cfg *setting.Cfg, store A Store: store, stateStore: stateStore, logger: l.New("component", "alertmanager", opts.TenantKey, opts.TenantID), // similar to what the base does + decryptFn: decryptFn, } return am, nil @@ -335,14 +337,22 @@ func (am *alertmanager) applyConfig(ctx context.Context, cfg *apimodels.Postable return false, nil } + receivers := PostableApiAlertingConfigToApiReceivers(cfg.AlertmanagerConfig) + for _, recv := range receivers { + err = patchNewSecureFields(ctx, recv, alertingNotify.DecodeSecretsFromBase64, am.decryptFn) + if err != nil { + return false, err + } + } + am.logger.Info("Applying new configuration to Alertmanager", "configHash", fmt.Sprintf("%x", configHash)) err = am.Base.ApplyConfig(alertingNotify.NotificationsConfiguration{ RoutingTree: cfg.AlertmanagerConfig.Route.AsAMRoute(), InhibitRules: cfg.AlertmanagerConfig.InhibitRules, MuteTimeIntervals: cfg.AlertmanagerConfig.MuteTimeIntervals, TimeIntervals: cfg.AlertmanagerConfig.TimeIntervals, Templates: ToTemplateDefinitions(cfg), - Receivers: PostableApiAlertingConfigToApiReceivers(cfg.AlertmanagerConfig), + Receivers: receivers, DispatcherLimits: &nilLimits{}, Raw: rawConfig, Hash: configHash, @@ -355,6 +365,50 @@ func (am *alertmanager) applyConfig(ctx context.Context, cfg *apimodels.Postable return true, nil } +func patchNewSecureFields(ctx context.Context, api *alertingNotify.APIReceiver, decode alertingNotify.DecodeSecretsFn, decrypt alertingNotify.GetDecryptedValueFn) error { + for _, integration := range api.Integrations { + switch integration.Type { + case "dingding": + err := patchSettingsFromSecureSettings(ctx, integration, "url", decode, decrypt) + if err != nil { + return err + } + } + } + return nil +} + +func patchSettingsFromSecureSettings(ctx context.Context, integration *alertingNotify.GrafanaIntegrationConfig, key string, decode alertingNotify.DecodeSecretsFn, decrypt alertingNotify.GetDecryptedValueFn) error { + if _, ok := integration.SecureSettings[key]; !ok { + return nil + } + decoded, err := decode(integration.SecureSettings) + if err != nil { + return err + } + settings := map[string]any{} + err = json.Unmarshal(integration.Settings, &settings) + if err != nil { + return err + } + currentValue, ok := settings[key] + currentString := "" + if ok { + currentString, _ = currentValue.(string) + } + secretValue := decrypt(ctx, decoded, key, currentString) + if secretValue == currentString { + return nil + } + settings[key] = secretValue + data, err := json.Marshal(settings) + if err != nil { + return err + } + integration.Settings = data + return nil +} + // PutAlerts receives the alerts and then sends them through the corresponding route based on whenever the alert has a receiver embedded or not func (am *alertmanager) PutAlerts(_ context.Context, postableAlerts apimodels.PostableAlerts) error { alerts := make(alertingNotify.PostableAlerts, 0, len(postableAlerts.PostableAlerts))
pkg/services/ngalert/notifier/channels_config/available_channels.go+1 −0 modified@@ -284,6 +284,7 @@ func GetAvailableNotifiers() []*NotifierPlugin { Placeholder: "https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxx", PropertyName: "url", Required: true, + Secure: true, }, { Label: "Message Type",
pkg/services/ngalert/notifier/channels_config/available_channels_test.go+1 −1 modified@@ -11,7 +11,7 @@ func TestGetSecretKeysForContactPointType(t *testing.T) { receiverType string expectedSecretFields []string }{ - {receiverType: "dingding", expectedSecretFields: []string{}}, + {receiverType: "dingding", expectedSecretFields: []string{"url"}}, {receiverType: "kafka", expectedSecretFields: []string{"password"}}, {receiverType: "email", expectedSecretFields: []string{}}, {receiverType: "pagerduty", expectedSecretFields: []string{"integrationKey"}},
pkg/services/ngalert/notifier/testreceivers.go+7 −2 modified@@ -24,12 +24,17 @@ func (am *alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei SecureSettings: gr.SecureSettings, }) } - receivers = append(receivers, &alertingNotify.APIReceiver{ + recv := &alertingNotify.APIReceiver{ ConfigReceiver: r.Receiver, GrafanaIntegrations: alertingNotify.GrafanaIntegrations{ Integrations: integrations, }, - }) + } + err := patchNewSecureFields(ctx, recv, alertingNotify.DecodeSecretsFromBase64, am.decryptFn) + if err != nil { + return nil, 0, err + } + receivers = append(receivers, recv) } a := &alertingNotify.PostableAlert{} if c.Alert != nil {
pkg/tests/api/alerting/api_notification_channel_test.go+2 −4 modified@@ -2088,10 +2088,8 @@ var expAlertmanagerConfigFromAPI = ` "name": "dingding_test", "type": "dingding", "disableResolveMessage": false, - "settings": { - "url": "http://CHANNEL_ADDR/dingding_recv/dingding_test" - }, - "secureFields": {} + "settings": {}, + "secureFields": {"url": true} } ] },
19c912476d4fdeclare dingding url as secret
4 files changed · +59 −3
pkg/services/ngalert/notifier/alertmanager.go+50 −0 modified@@ -385,6 +385,10 @@ func (am *alertmanager) AppURL() string { // buildReceiverIntegrations builds a list of integration notifiers off of a receiver config. func (am *alertmanager) buildReceiverIntegrations(receiver *alertingNotify.APIReceiver, tmpl *alertingTemplates.Template) ([]*alertingNotify.Integration, error) { + err := patchNewSecureFields(context.Background(), receiver, am.decryptFn) + if err != nil { + return nil, err + } receiverCfg, err := alertingNotify.BuildReceiverConfiguration(context.Background(), receiver, am.decryptFn) if err != nil { return nil, err @@ -411,6 +415,52 @@ func (am *alertmanager) buildReceiverIntegrations(receiver *alertingNotify.APIRe return integrations, nil } +func patchNewSecureFields(ctx context.Context, api *alertingNotify.APIReceiver, decrypt alertingNotify.GetDecryptedValueFn) error { + for _, integration := range api.Integrations { + switch integration.Type { + case "dingding": + err := patchSettingsFromSecureSettings(ctx, integration, "url", decrypt) + if err != nil { + return err + } + } + } + return nil +} + +func patchSettingsFromSecureSettings(ctx context.Context, integration *alertingNotify.GrafanaIntegrationConfig, key string, decrypt alertingNotify.GetDecryptedValueFn) error { + var encrypted string + var ok bool + if encrypted, ok = integration.SecureSettings[key]; !ok { + return nil + } + decoded, err := decode(encrypted) + if err != nil { + return err + } + settings := map[string]any{} + err = json.Unmarshal(integration.Settings, &settings) + if err != nil { + return err + } + currentValue, ok := settings[key] + currentString := "" + if ok { + currentString, _ = currentValue.(string) + } + secretValue := decrypt(ctx, map[string][]byte{key: decoded}, key, currentString) + if secretValue == currentString { + return nil + } + settings[key] = secretValue + data, err := json.Marshal(settings) + if err != nil { + return err + } + integration.Settings = data + return nil +} + // PutAlerts receives the alerts and then sends them through the corresponding route based on whenever the alert has a receiver embedded or not func (am *alertmanager) PutAlerts(_ context.Context, postableAlerts apimodels.PostableAlerts) error { alerts := make(alertingNotify.PostableAlerts, 0, len(postableAlerts.PostableAlerts))
pkg/services/ngalert/notifier/channels_config/available_channels.go+1 −0 modified@@ -126,6 +126,7 @@ func GetAvailableNotifiers() []*NotifierPlugin { Placeholder: "https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxx", PropertyName: "url", Required: true, + Secure: true, }, { Label: "Message Type",
pkg/services/ngalert/notifier/channels_config/available_channels_test.go+1 −1 modified@@ -11,7 +11,7 @@ func TestGetSecretKeysForContactPointType(t *testing.T) { receiverType string expectedSecretFields []string }{ - {receiverType: "dingding", expectedSecretFields: []string{}}, + {receiverType: "dingding", expectedSecretFields: []string{"url"}}, {receiverType: "kafka", expectedSecretFields: []string{"password"}}, {receiverType: "email", expectedSecretFields: []string{}}, {receiverType: "pagerduty", expectedSecretFields: []string{"integrationKey"}},
pkg/services/ngalert/notifier/testreceivers.go+7 −2 modified@@ -23,12 +23,17 @@ func (am *alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei SecureSettings: gr.SecureSettings, }) } - receivers = append(receivers, &alertingNotify.APIReceiver{ + recv := &alertingNotify.APIReceiver{ ConfigReceiver: r.Receiver, GrafanaIntegrations: alertingNotify.GrafanaIntegrations{ Integrations: integrations, }, - }) + } + err := patchNewSecureFields(ctx, recv, am.decryptFn) + if err != nil { + return nil, 0, err + } + receivers = append(receivers, recv) } var alert *alertingNotify.TestReceiversConfigAlertParams if c.Alert != nil {
0adb869188fadeclare dingding url as secret
4 files changed · +57 −3
pkg/services/ngalert/notifier/alertmanager.go+48 −0 modified@@ -387,6 +387,10 @@ func (am *alertmanager) AppURL() string { // buildReceiverIntegrations builds a list of integration notifiers off of a receiver config. func (am *alertmanager) buildReceiverIntegrations(receiver *alertingNotify.APIReceiver, tmpl *alertingTemplates.Template) ([]*alertingNotify.Integration, error) { + err := patchNewSecureFields(context.Background(), receiver, alertingNotify.DecodeSecretsFromBase64, am.decryptFn) + if err != nil { + return nil, err + } receiverCfg, err := alertingNotify.BuildReceiverConfiguration(context.Background(), receiver, alertingNotify.DecodeSecretsFromBase64, am.decryptFn) if err != nil { return nil, err @@ -414,6 +418,50 @@ func (am *alertmanager) buildReceiverIntegrations(receiver *alertingNotify.APIRe return integrations, nil } +func patchNewSecureFields(ctx context.Context, api *alertingNotify.APIReceiver, decode alertingNotify.DecodeSecretsFn, decrypt alertingNotify.GetDecryptedValueFn) error { + for _, integration := range api.Integrations { + switch integration.Type { + case "dingding": + err := patchSettingsFromSecureSettings(ctx, integration, "url", decode, decrypt) + if err != nil { + return err + } + } + } + return nil +} + +func patchSettingsFromSecureSettings(ctx context.Context, integration *alertingNotify.GrafanaIntegrationConfig, key string, decode alertingNotify.DecodeSecretsFn, decrypt alertingNotify.GetDecryptedValueFn) error { + if _, ok := integration.SecureSettings[key]; !ok { + return nil + } + decoded, err := decode(integration.SecureSettings) + if err != nil { + return err + } + settings := map[string]any{} + err = json.Unmarshal(integration.Settings, &settings) + if err != nil { + return err + } + currentValue, ok := settings[key] + currentString := "" + if ok { + currentString, _ = currentValue.(string) + } + secretValue := decrypt(ctx, decoded, key, currentString) + if secretValue == currentString { + return nil + } + settings[key] = secretValue + data, err := json.Marshal(settings) + if err != nil { + return err + } + integration.Settings = data + return nil +} + // PutAlerts receives the alerts and then sends them through the corresponding route based on whenever the alert has a receiver embedded or not func (am *alertmanager) PutAlerts(_ context.Context, postableAlerts apimodels.PostableAlerts) error { alerts := make(alertingNotify.PostableAlerts, 0, len(postableAlerts.PostableAlerts))
pkg/services/ngalert/notifier/channels_config/available_channels.go+1 −0 modified@@ -127,6 +127,7 @@ func GetAvailableNotifiers() []*NotifierPlugin { Placeholder: "https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxx", PropertyName: "url", Required: true, + Secure: true, }, { Label: "Message Type",
pkg/services/ngalert/notifier/channels_config/available_channels_test.go+1 −1 modified@@ -11,7 +11,7 @@ func TestGetSecretKeysForContactPointType(t *testing.T) { receiverType string expectedSecretFields []string }{ - {receiverType: "dingding", expectedSecretFields: []string{}}, + {receiverType: "dingding", expectedSecretFields: []string{"url"}}, {receiverType: "kafka", expectedSecretFields: []string{"password"}}, {receiverType: "email", expectedSecretFields: []string{}}, {receiverType: "pagerduty", expectedSecretFields: []string{"integrationKey"}},
pkg/services/ngalert/notifier/testreceivers.go+7 −2 modified@@ -24,12 +24,17 @@ func (am *alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei SecureSettings: gr.SecureSettings, }) } - receivers = append(receivers, &alertingNotify.APIReceiver{ + recv := &alertingNotify.APIReceiver{ ConfigReceiver: r.Receiver, GrafanaIntegrations: alertingNotify.GrafanaIntegrations{ Integrations: integrations, }, - }) + } + err := patchNewSecureFields(ctx, recv, alertingNotify.DecodeSecretsFromBase64, am.decryptFn) + if err != nil { + return nil, 0, err + } + receivers = append(receivers, recv) } a := &alertingNotify.PostableAlert{} if c.Alert != nil {
4fc33647a829declare dingding url as secret
4 files changed · +59 −3
pkg/services/ngalert/notifier/alertmanager.go+50 −0 modified@@ -385,6 +385,10 @@ func (am *alertmanager) AppURL() string { // buildReceiverIntegrations builds a list of integration notifiers off of a receiver config. func (am *alertmanager) buildReceiverIntegrations(receiver *alertingNotify.APIReceiver, tmpl *alertingTemplates.Template) ([]*alertingNotify.Integration, error) { + err := patchNewSecureFields(context.Background(), receiver, am.decryptFn) + if err != nil { + return nil, err + } receiverCfg, err := alertingNotify.BuildReceiverConfiguration(context.Background(), receiver, am.decryptFn) if err != nil { return nil, err @@ -411,6 +415,52 @@ func (am *alertmanager) buildReceiverIntegrations(receiver *alertingNotify.APIRe return integrations, nil } +func patchNewSecureFields(ctx context.Context, api *alertingNotify.APIReceiver, decrypt alertingNotify.GetDecryptedValueFn) error { + for _, integration := range api.Integrations { + switch integration.Type { + case "dingding": + err := patchSettingsFromSecureSettings(ctx, integration, "url", decrypt) + if err != nil { + return err + } + } + } + return nil +} + +func patchSettingsFromSecureSettings(ctx context.Context, integration *alertingNotify.GrafanaIntegrationConfig, key string, decrypt alertingNotify.GetDecryptedValueFn) error { + var encrypted string + var ok bool + if encrypted, ok = integration.SecureSettings[key]; !ok { + return nil + } + decoded, err := decode(encrypted) + if err != nil { + return err + } + settings := map[string]any{} + err = json.Unmarshal(integration.Settings, &settings) + if err != nil { + return err + } + currentValue, ok := settings[key] + currentString := "" + if ok { + currentString, _ = currentValue.(string) + } + secretValue := decrypt(ctx, map[string][]byte{key: decoded}, key, currentString) + if secretValue == currentString { + return nil + } + settings[key] = secretValue + data, err := json.Marshal(settings) + if err != nil { + return err + } + integration.Settings = data + return nil +} + // PutAlerts receives the alerts and then sends them through the corresponding route based on whenever the alert has a receiver embedded or not func (am *alertmanager) PutAlerts(_ context.Context, postableAlerts apimodels.PostableAlerts) error { alerts := make(alertingNotify.PostableAlerts, 0, len(postableAlerts.PostableAlerts))
pkg/services/ngalert/notifier/channels_config/available_channels.go+1 −0 modified@@ -126,6 +126,7 @@ func GetAvailableNotifiers() []*NotifierPlugin { Placeholder: "https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxx", PropertyName: "url", Required: true, + Secure: true, }, { Label: "Message Type",
pkg/services/ngalert/notifier/channels_config/available_channels_test.go+1 −1 modified@@ -11,7 +11,7 @@ func TestGetSecretKeysForContactPointType(t *testing.T) { receiverType string expectedSecretFields []string }{ - {receiverType: "dingding", expectedSecretFields: []string{}}, + {receiverType: "dingding", expectedSecretFields: []string{"url"}}, {receiverType: "kafka", expectedSecretFields: []string{"password"}}, {receiverType: "email", expectedSecretFields: []string{}}, {receiverType: "pagerduty", expectedSecretFields: []string{"integrationKey"}},
pkg/services/ngalert/notifier/testreceivers.go+7 −2 modified@@ -23,12 +23,17 @@ func (am *alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei SecureSettings: gr.SecureSettings, }) } - receivers = append(receivers, &alertingNotify.APIReceiver{ + recv := &alertingNotify.APIReceiver{ ConfigReceiver: r.Receiver, GrafanaIntegrations: alertingNotify.GrafanaIntegrations{ Integrations: integrations, }, - }) + } + err := patchNewSecureFields(ctx, recv, am.decryptFn) + if err != nil { + return nil, 0, err + } + receivers = append(receivers, recv) } var alert *alertingNotify.TestReceiversConfigAlertParams if c.Alert != nil {
a78de30720b4declare dingding url as secret
3 files changed · +58 −2
pkg/services/ngalert/notifier/alertmanager.go+50 −0 modified@@ -380,6 +380,10 @@ func (am *alertmanager) AppURL() string { // buildReceiverIntegrations builds a list of integration notifiers off of a receiver config. func (am *alertmanager) buildReceiverIntegrations(receiver *alertingNotify.APIReceiver, tmpl *alertingTemplates.Template) ([]*alertingNotify.Integration, error) { + err := patchNewSecureFields(context.Background(), receiver, am.decryptFn) + if err != nil { + return nil, err + } receiverCfg, err := alertingNotify.BuildReceiverConfiguration(context.Background(), receiver, am.decryptFn) if err != nil { return nil, err @@ -406,6 +410,52 @@ func (am *alertmanager) buildReceiverIntegrations(receiver *alertingNotify.APIRe return integrations, nil } +func patchNewSecureFields(ctx context.Context, api *alertingNotify.APIReceiver, decrypt alertingNotify.GetDecryptedValueFn) error { + for _, integration := range api.Integrations { + switch integration.Type { + case "dingding": + err := patchSettingsFromSecureSettings(ctx, integration, "url", decrypt) + if err != nil { + return err + } + } + } + return nil +} + +func patchSettingsFromSecureSettings(ctx context.Context, integration *alertingNotify.GrafanaIntegrationConfig, key string, decrypt alertingNotify.GetDecryptedValueFn) error { + var encrypted string + var ok bool + if encrypted, ok = integration.SecureSettings[key]; !ok { + return nil + } + decoded, err := decode(encrypted) + if err != nil { + return err + } + settings := map[string]any{} + err = json.Unmarshal(integration.Settings, &settings) + if err != nil { + return err + } + currentValue, ok := settings[key] + currentString := "" + if ok { + currentString, _ = currentValue.(string) + } + secretValue := decrypt(ctx, map[string][]byte{key: decoded}, key, currentString) + if secretValue == currentString { + return nil + } + settings[key] = secretValue + data, err := json.Marshal(settings) + if err != nil { + return err + } + integration.Settings = data + return nil +} + // PutAlerts receives the alerts and then sends them through the corresponding route based on whenever the alert has a receiver embedded or not func (am *alertmanager) PutAlerts(_ context.Context, postableAlerts apimodels.PostableAlerts) error { alerts := make(alertingNotify.PostableAlerts, 0, len(postableAlerts.PostableAlerts))
pkg/services/ngalert/notifier/channels_config/available_channels.go+1 −0 modified@@ -125,6 +125,7 @@ func GetAvailableNotifiers() []*NotifierPlugin { Placeholder: "https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxx", PropertyName: "url", Required: true, + Secure: true, }, { Label: "Message Type",
pkg/services/ngalert/notifier/testreceivers.go+7 −2 modified@@ -44,12 +44,17 @@ func (am *alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei SecureSettings: gr.SecureSettings, }) } - receivers = append(receivers, &alertingNotify.APIReceiver{ + recv := &alertingNotify.APIReceiver{ ConfigReceiver: r.Receiver, GrafanaIntegrations: alertingNotify.GrafanaIntegrations{ Integrations: integrations, }, - }) + } + err := patchNewSecureFields(ctx, recv, am.decryptFn) + if err != nil { + return nil, err + } + receivers = append(receivers, recv) } var alert *alertingNotify.TestReceiversConfigAlertParams if c.Alert != nil {
04111e9f2afddeclare dingding url as secret
3 files changed · +58 −2
pkg/services/ngalert/notifier/alertmanager.go+50 −0 modified@@ -388,6 +388,10 @@ func (am *alertmanager) AppURL() string { // buildReceiverIntegrations builds a list of integration notifiers off of a receiver config. func (am *alertmanager) buildReceiverIntegrations(receiver *alertingNotify.APIReceiver, tmpl *alertingTemplates.Template) ([]*alertingNotify.Integration, error) { + err := patchNewSecureFields(context.Background(), receiver, am.decryptFn) + if err != nil { + return nil, err + } receiverCfg, err := alertingNotify.BuildReceiverConfiguration(context.Background(), receiver, am.decryptFn) if err != nil { return nil, err @@ -414,6 +418,52 @@ func (am *alertmanager) buildReceiverIntegrations(receiver *alertingNotify.APIRe return integrations, nil } +func patchNewSecureFields(ctx context.Context, api *alertingNotify.APIReceiver, decrypt alertingNotify.GetDecryptedValueFn) error { + for _, integration := range api.Integrations { + switch integration.Type { + case "dingding": + err := patchSettingsFromSecureSettings(ctx, integration, "url", decrypt) + if err != nil { + return err + } + } + } + return nil +} + +func patchSettingsFromSecureSettings(ctx context.Context, integration *alertingNotify.GrafanaIntegrationConfig, key string, decrypt alertingNotify.GetDecryptedValueFn) error { + var encrypted string + var ok bool + if encrypted, ok = integration.SecureSettings[key]; !ok { + return nil + } + decoded, err := decode(encrypted) + if err != nil { + return err + } + settings := map[string]any{} + err = json.Unmarshal(integration.Settings, &settings) + if err != nil { + return err + } + currentValue, ok := settings[key] + currentString := "" + if ok { + currentString, _ = currentValue.(string) + } + secretValue := decrypt(ctx, map[string][]byte{key: decoded}, key, currentString) + if secretValue == currentString { + return nil + } + settings[key] = secretValue + data, err := json.Marshal(settings) + if err != nil { + return err + } + integration.Settings = data + return nil +} + // PutAlerts receives the alerts and then sends them through the corresponding route based on whenever the alert has a receiver embedded or not func (am *alertmanager) PutAlerts(_ context.Context, postableAlerts apimodels.PostableAlerts) error { alerts := make(alertingNotify.PostableAlerts, 0, len(postableAlerts.PostableAlerts))
pkg/services/ngalert/notifier/channels_config/available_channels.go+1 −0 modified@@ -124,6 +124,7 @@ func GetAvailableNotifiers() []*NotifierPlugin { Placeholder: "https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxx", PropertyName: "url", Required: true, + Secure: true, }, { Label: "Message Type",
pkg/services/ngalert/notifier/testreceivers.go+7 −2 modified@@ -46,12 +46,17 @@ func (am *alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei SecureSettings: gr.SecureSettings, }) } - receivers = append(receivers, &alertingNotify.APIReceiver{ + recv := &alertingNotify.APIReceiver{ ConfigReceiver: r.Receiver, GrafanaIntegrations: alertingNotify.GrafanaIntegrations{ Integrations: integrations, }, - }) + } + err := patchNewSecureFields(ctx, recv, am.decryptFn) + if err != nil { + return nil, err + } + receivers = append(receivers, recv) } var alert *alertingNotify.TestReceiversConfigAlertParams if c.Alert != nil {
4144c636d1a6declare dingding url as secret
4 files changed · +57 −3
pkg/services/ngalert/notifier/alertmanager.go+48 −0 modified@@ -398,6 +398,10 @@ func (am *alertmanager) AppURL() string { // buildReceiverIntegrations builds a list of integration notifiers off of a receiver config. func (am *alertmanager) buildReceiverIntegrations(receiver *alertingNotify.APIReceiver, tmpl *alertingTemplates.Template) ([]*alertingNotify.Integration, error) { + err := patchNewSecureFields(context.Background(), receiver, alertingNotify.DecodeSecretsFromBase64, am.decryptFn) + if err != nil { + return nil, err + } receiverCfg, err := alertingNotify.BuildReceiverConfiguration(context.Background(), receiver, alertingNotify.DecodeSecretsFromBase64, am.decryptFn) if err != nil { return nil, err @@ -422,6 +426,50 @@ func (am *alertmanager) buildReceiverIntegrations(receiver *alertingNotify.APIRe return integrations, nil } +func patchNewSecureFields(ctx context.Context, api *alertingNotify.APIReceiver, decode alertingNotify.DecodeSecretsFn, decrypt alertingNotify.GetDecryptedValueFn) error { + for _, integration := range api.Integrations { + switch integration.Type { + case "dingding": + err := patchSettingsFromSecureSettings(ctx, integration, "url", decode, decrypt) + if err != nil { + return err + } + } + } + return nil +} + +func patchSettingsFromSecureSettings(ctx context.Context, integration *alertingNotify.GrafanaIntegrationConfig, key string, decode alertingNotify.DecodeSecretsFn, decrypt alertingNotify.GetDecryptedValueFn) error { + if _, ok := integration.SecureSettings[key]; !ok { + return nil + } + decoded, err := decode(integration.SecureSettings) + if err != nil { + return err + } + settings := map[string]any{} + err = json.Unmarshal(integration.Settings, &settings) + if err != nil { + return err + } + currentValue, ok := settings[key] + currentString := "" + if ok { + currentString, _ = currentValue.(string) + } + secretValue := decrypt(ctx, decoded, key, currentString) + if secretValue == currentString { + return nil + } + settings[key] = secretValue + data, err := json.Marshal(settings) + if err != nil { + return err + } + integration.Settings = data + return nil +} + // PutAlerts receives the alerts and then sends them through the corresponding route based on whenever the alert has a receiver embedded or not func (am *alertmanager) PutAlerts(_ context.Context, postableAlerts apimodels.PostableAlerts) error { alerts := make(alertingNotify.PostableAlerts, 0, len(postableAlerts.PostableAlerts))
pkg/services/ngalert/notifier/channels_config/available_channels.go+1 −0 modified@@ -127,6 +127,7 @@ func GetAvailableNotifiers() []*NotifierPlugin { Placeholder: "https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxx", PropertyName: "url", Required: true, + Secure: true, }, { Label: "Message Type",
pkg/services/ngalert/notifier/channels_config/available_channels_test.go+1 −1 modified@@ -11,7 +11,7 @@ func TestGetSecretKeysForContactPointType(t *testing.T) { receiverType string expectedSecretFields []string }{ - {receiverType: "dingding", expectedSecretFields: []string{}}, + {receiverType: "dingding", expectedSecretFields: []string{"url"}}, {receiverType: "kafka", expectedSecretFields: []string{"password"}}, {receiverType: "email", expectedSecretFields: []string{}}, {receiverType: "pagerduty", expectedSecretFields: []string{"integrationKey"}},
pkg/services/ngalert/notifier/testreceivers.go+7 −2 modified@@ -23,12 +23,17 @@ func (am *alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei SecureSettings: gr.SecureSettings, }) } - receivers = append(receivers, &alertingNotify.APIReceiver{ + recv := &alertingNotify.APIReceiver{ ConfigReceiver: r.Receiver, GrafanaIntegrations: alertingNotify.GrafanaIntegrations{ Integrations: integrations, }, - }) + } + err := patchNewSecureFields(ctx, recv, alertingNotify.DecodeSecretsFromBase64, am.decryptFn) + if err != nil { + return nil, 0, err + } + receivers = append(receivers, recv) } var alert *alertingNotify.TestReceiversConfigAlertParams if c.Alert != nil {
91327938626cdeclare dingding url as secret
4 files changed · +59 −3
pkg/services/ngalert/notifier/alertmanager.go+50 −0 modified@@ -385,6 +385,10 @@ func (am *alertmanager) AppURL() string { // buildReceiverIntegrations builds a list of integration notifiers off of a receiver config. func (am *alertmanager) buildReceiverIntegrations(receiver *alertingNotify.APIReceiver, tmpl *alertingTemplates.Template) ([]*alertingNotify.Integration, error) { + err := patchNewSecureFields(context.Background(), receiver, am.decryptFn) + if err != nil { + return nil, err + } receiverCfg, err := alertingNotify.BuildReceiverConfiguration(context.Background(), receiver, am.decryptFn) if err != nil { return nil, err @@ -411,6 +415,52 @@ func (am *alertmanager) buildReceiverIntegrations(receiver *alertingNotify.APIRe return integrations, nil } +func patchNewSecureFields(ctx context.Context, api *alertingNotify.APIReceiver, decrypt alertingNotify.GetDecryptedValueFn) error { + for _, integration := range api.Integrations { + switch integration.Type { + case "dingding": + err := patchSettingsFromSecureSettings(ctx, integration, "url", decrypt) + if err != nil { + return err + } + } + } + return nil +} + +func patchSettingsFromSecureSettings(ctx context.Context, integration *alertingNotify.GrafanaIntegrationConfig, key string, decrypt alertingNotify.GetDecryptedValueFn) error { + var encrypted string + var ok bool + if encrypted, ok = integration.SecureSettings[key]; !ok { + return nil + } + decoded, err := decode(encrypted) + if err != nil { + return err + } + settings := map[string]any{} + err = json.Unmarshal(integration.Settings, &settings) + if err != nil { + return err + } + currentValue, ok := settings[key] + currentString := "" + if ok { + currentString, _ = currentValue.(string) + } + secretValue := decrypt(ctx, map[string][]byte{key: decoded}, key, currentString) + if secretValue == currentString { + return nil + } + settings[key] = secretValue + data, err := json.Marshal(settings) + if err != nil { + return err + } + integration.Settings = data + return nil +} + // PutAlerts receives the alerts and then sends them through the corresponding route based on whenever the alert has a receiver embedded or not func (am *alertmanager) PutAlerts(_ context.Context, postableAlerts apimodels.PostableAlerts) error { alerts := make(alertingNotify.PostableAlerts, 0, len(postableAlerts.PostableAlerts))
pkg/services/ngalert/notifier/channels_config/available_channels.go+1 −0 modified@@ -126,6 +126,7 @@ func GetAvailableNotifiers() []*NotifierPlugin { Placeholder: "https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxx", PropertyName: "url", Required: true, + Secure: true, }, { Label: "Message Type",
pkg/services/ngalert/notifier/channels_config/available_channels_test.go+1 −1 modified@@ -11,7 +11,7 @@ func TestGetSecretKeysForContactPointType(t *testing.T) { receiverType string expectedSecretFields []string }{ - {receiverType: "dingding", expectedSecretFields: []string{}}, + {receiverType: "dingding", expectedSecretFields: []string{"url"}}, {receiverType: "kafka", expectedSecretFields: []string{"password"}}, {receiverType: "email", expectedSecretFields: []string{}}, {receiverType: "pagerduty", expectedSecretFields: []string{"integrationKey"}},
pkg/services/ngalert/notifier/testreceivers.go+7 −2 modified@@ -23,12 +23,17 @@ func (am *alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei SecureSettings: gr.SecureSettings, }) } - receivers = append(receivers, &alertingNotify.APIReceiver{ + recv := &alertingNotify.APIReceiver{ ConfigReceiver: r.Receiver, GrafanaIntegrations: alertingNotify.GrafanaIntegrations{ Integrations: integrations, }, - }) + } + err := patchNewSecureFields(ctx, recv, am.decryptFn) + if err != nil { + return nil, 0, err + } + receivers = append(receivers, recv) } var alert *alertingNotify.TestReceiversConfigAlertParams if c.Alert != nil {
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
11- github.com/advisories/GHSA-46m5-8hpj-p5p5ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-3415ghsaADVISORY
- github.com/grafana/grafana/commit/04111e9f2afd95ea3e5b01865cc29d3fc1198e71ghsaWEB
- github.com/grafana/grafana/commit/0adb869188fa2b9ae26efd424b94e17189538f29ghsaWEB
- github.com/grafana/grafana/commit/19c912476d4f7a81e8a3562668bc38f31b909e18ghsaWEB
- github.com/grafana/grafana/commit/4144c636d1a6d0b17fafcf7a2c40fa403542202aghsaWEB
- github.com/grafana/grafana/commit/4fc33647a8297d3a0aae04a5fcbac883ceb6a655ghsaWEB
- github.com/grafana/grafana/commit/910eb1dd9e618014c6b1d2a99a431b99d4268c05ghsaWEB
- github.com/grafana/grafana/commit/91327938626c9426e481e6294850af7b61415c98ghsaWEB
- github.com/grafana/grafana/commit/a78de30720b4f33c88d0c1a973e693ebf3831717ghsaWEB
- grafana.com/security/security-advisories/cve-2025-3415nvdWEB
News mentions
0No linked articles in our index yet.