From 4653ffc4169d71dc761bcc091f05a4fd6a8db14b Mon Sep 17 00:00:00 2001 From: otmyards-crypto <245061101+otmyards-crypto@users.noreply.github.com> Date: Sun, 24 May 2026 20:18:08 +0000 Subject: [PATCH] notify/telegram: wrap Send error with notify.RedactURL telegram.go was the only notifier not applying notify.RedactURL to errors from the underlying HTTP client. When telebot's Send call fails for transport reasons (e.g. dial timeout), it returns a *url.Error that includes the full API URL, which contains the bot token as a path segment (https://api.telegram.org/bot/...). This error propagated verbatim into the slog attributes logged by notify.go and dispatch.go, leaking the token. All other notifiers (webhook, slack, discord, msteams, etc.) already call notify.RedactURL on their HTTP errors following PR #3887. Apply the same pattern to the one remaining call site in telegram.go. Note: getBotToken errors are local file-read errors with no URL, and createTelegramClient / telebot.NewBot runs in Offline mode and never contacts the API, so neither site needs wrapping. Adds a unit test covering both error paths: - transport error (*url.Error): token redacted, URL shows - Telegram API error (non-*url.Error): RedactURL is a no-op, but telebot's own error message does not include the URL or token. Signed-off-by: otmyards-crypto <245061101+otmyards-crypto@users.noreply.github.com> Co-Authored-By: Paperclip --- notify/telegram/telegram.go | 2 +- notify/telegram/telegram_test.go | 73 ++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/notify/telegram/telegram.go b/notify/telegram/telegram.go index 07bbedbbe8..8d1cf2d68d 100644 --- a/notify/telegram/telegram.go +++ b/notify/telegram/telegram.go @@ -119,7 +119,7 @@ func (n *Notifier) Notify(ctx context.Context, alert ...*types.Alert) (bool, err ParseMode: n.conf.ParseMode, }) if err != nil { - return true, err + return true, notify.RedactURL(err) } logger.Debug("Telegram message successfully published", "message_id", message.ID, "chat_id", message.Chat.ID) diff --git a/notify/telegram/telegram_test.go b/notify/telegram/telegram_test.go index 7192d493fd..22dd75e98c 100644 --- a/notify/telegram/telegram_test.go +++ b/notify/telegram/telegram_test.go @@ -213,3 +213,76 @@ func TestTelegramNotify(t *testing.T) { }) } } + +// TestTelegramNotifyRedactURL verifies that notify.RedactURL is applied to the +// error returned by client.Send, so the bot token is never exposed in logs. +func TestTelegramNotifyRedactURL(t *testing.T) { + token := "secret" + + t.Run("transport error redacts URL", func(t *testing.T) { + // Point at a closed server so telebot returns a *url.Error with the full URL. + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + u, _ := url.Parse(srv.URL) + srv.Close() // closed immediately — next dial will fail + + notifier, err := New( + &config.TelegramConfig{ + HTTPConfig: &commoncfg.HTTPClientConfig{}, + APIUrl: &amcommoncfg.URL{URL: u}, + BotToken: commoncfg.Secret(token), + }, + test.CreateTmpl(t), + promslog.NewNopLogger(), + ) + require.NoError(t, err) + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + ctx = notify.WithGroupKey(ctx, "1") + + retry, err := notifier.Notify(ctx, &types.Alert{ + Alert: model.Alert{Labels: model.LabelSet{"alertname": "test"}}, + }) + require.True(t, retry) + require.Error(t, err) + // The token must not appear in the error string. + require.NotContains(t, err.Error(), token, "bot token leaked in transport error") + // The URL should be redacted. + require.Contains(t, err.Error(), "") + }) + + t.Run("Telegram API error passes through without token", func(t *testing.T) { + // Return a Telegram API error response — telebot wraps this as its own + // error type (not *url.Error), so RedactURL is a no-op, but the token + // is not in the error string either. + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"ok":false,"description":"Bad Request: chat not found","error_code":400}`)) + })) + defer srv.Close() + u, _ := url.Parse(srv.URL) + + notifier, err := New( + &config.TelegramConfig{ + HTTPConfig: &commoncfg.HTTPClientConfig{}, + APIUrl: &amcommoncfg.URL{URL: u}, + BotToken: commoncfg.Secret(token), + }, + test.CreateTmpl(t), + promslog.NewNopLogger(), + ) + require.NoError(t, err) + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + ctx = notify.WithGroupKey(ctx, "1") + + retry, err := notifier.Notify(ctx, &types.Alert{ + Alert: model.Alert{Labels: model.LabelSet{"alertname": "test"}}, + }) + require.True(t, retry) + require.Error(t, err) + require.NotContains(t, err.Error(), token, "bot token leaked in API error") + }) +} +}