From 97ca84bbfcf588300865bd02542dabd633571120 Mon Sep 17 00:00:00 2001 From: MengMengCode Date: Sun, 9 Aug 2026 21:14:07 +0800 Subject: [PATCH] fix: redact Telegram tokens from errors --- internal/server/telegram_bot.go | 30 +++++++++++++++++++++++----- internal/server/telegram_bot_test.go | 10 ++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/internal/server/telegram_bot.go b/internal/server/telegram_bot.go index 001da6b..53040c5 100644 --- a/internal/server/telegram_bot.go +++ b/internal/server/telegram_bot.go @@ -11,6 +11,7 @@ import ( "io" "net/http" "net/http/httptest" + "regexp" "strconv" "strings" "sync" @@ -30,6 +31,8 @@ const ( telegramMaxDialDuration = 10 * time.Minute ) +var telegramTokenInURLPattern = regexp.MustCompile(`bot[0-9]{5,20}:[A-Za-z0-9_-]{20,128}`) + type telegramRuntimeConfig struct { Token string ChatID string @@ -146,6 +149,9 @@ func (bot *telegramBot) poll(ctx context.Context) { updates, pollErr := bot.getUpdates(pollContext, config, offset, 5) cancel() if pollErr != nil { + if ctx.Err() != nil { + return + } bot.warn("poll Telegram updates", pollErr) if !waitTelegram(ctx, telegramPollInterval) { return @@ -970,7 +976,7 @@ func (bot *telegramBot) loadConfig(ctx context.Context) (telegramRuntimeConfig, func (bot *telegramBot) call(ctx context.Context, config telegramRuntimeConfig, method string, payload any, result any) error { base, err := validateTelegramAPIURL(ctx, config.BaseURL, config.Token, method) if err != nil { - return err + return redactTelegramError(err, config.Token) } body, err := json.Marshal(payload) if err != nil { @@ -982,13 +988,13 @@ func (bot *telegramBot) call(ctx context.Context, config telegramRuntimeConfig, } request, err := http.NewRequestWithContext(ctx, http.MethodPost, base.String(), bytes.NewReader(body)) if err != nil { - return err + return redactTelegramError(err, config.Token) } request.Header.Set("Content-Type", "application/json") request.Header.Set("User-Agent", "vocat-telegram-bot/1") response, err := client.Do(request) if err != nil { - return err + return redactTelegramError(err, config.Token) } defer response.Body.Close() responseBody, err := io.ReadAll(io.LimitReader(response.Body, 2<<20)) @@ -1042,7 +1048,7 @@ func (bot *telegramBot) warn(message string, err error) { return } now := time.Now() - text := err.Error() + text := redactTelegramText(err.Error(), "") bot.logMu.Lock() if text == bot.lastLogText && now.Sub(bot.lastLogTime) < time.Minute { bot.logMu.Unlock() @@ -1050,7 +1056,21 @@ func (bot *telegramBot) warn(message string, err error) { } bot.lastLogText, bot.lastLogTime = text, now bot.logMu.Unlock() - bot.server.logger.Warn(message, "error", err) + bot.server.logger.Warn(message, "error", text) +} + +func redactTelegramError(err error, token string) error { + if err == nil { + return nil + } + return errors.New(redactTelegramText(err.Error(), token)) +} + +func redactTelegramText(value, token string) string { + if strings.TrimSpace(token) != "" { + value = strings.ReplaceAll(value, token, "[REDACTED]") + } + return telegramTokenInURLPattern.ReplaceAllString(value, "bot[REDACTED]") } func parseTelegramCommand(text string) (string, string) { diff --git a/internal/server/telegram_bot_test.go b/internal/server/telegram_bot_test.go index c7131e3..4c2d8bb 100644 --- a/internal/server/telegram_bot_test.go +++ b/internal/server/telegram_bot_test.go @@ -2,6 +2,7 @@ package server import ( "context" + "errors" "strings" "testing" "time" @@ -171,3 +172,12 @@ func TestTelegramExecutesInteractiveUSSDForConfiguredDevice(t *testing.T) { } } } + +func TestTelegramErrorsRedactBotTokens(t *testing.T) { + token := "1234567890:abcdefghijklmnopqrstuvwxyzABCDE" + err := errors.New(`Post "https://api.telegram.org/bot` + token + `/getUpdates": context canceled`) + redacted := redactTelegramError(err, token) + if strings.Contains(redacted.Error(), token) || !strings.Contains(redacted.Error(), "bot[REDACTED]") { + t.Fatalf("redacted error = %q", redacted) + } +}