mirror of
https://github.com/tiennm99/openai-status-bot.git
synced 2026-10-13 18:16:01 +00:00
fix(telegram): redact bot token from transport error messages
Transport-level failures return *url.Error embedding the full request URL, which contains the bot token. Strip the token before the error propagates to logs.
This commit is contained in:
1 parent
546e6fcde2
commit
0e7967feb6
2 files changed
+43
-1
No files matched your search
@@ -15,6 +15,7 @@ import (
|
||||
|
||||
type Client struct {
|
||||
baseURL string
|
||||
token string
|
||||
httpClient *http.Client
|
||||
requestTimeout time.Duration
|
||||
}
|
||||
@@ -66,6 +67,7 @@ func IsTerminalSendError(err error) bool {
|
||||
func NewClient(token string, timeout time.Duration) *Client {
|
||||
return &Client{
|
||||
baseURL: "https://api.telegram.org/bot" + token,
|
||||
token: token,
|
||||
httpClient: &http.Client{},
|
||||
requestTimeout: timeout,
|
||||
}
|
||||
@@ -124,6 +126,20 @@ func (c *Client) SendText(ctx context.Context, chatID int64, threadID *int, text
|
||||
return c.postJSON(ctx, "/sendMessage", payload, &result, c.requestTimeout)
|
||||
}
|
||||
|
||||
// redactToken returns an error whose message has the bot token replaced with a
|
||||
// placeholder. It leaves the error untouched when no token is set or the token
|
||||
// does not appear in the message, so typed errors are preserved where possible.
|
||||
func (c *Client) redactToken(err error) error {
|
||||
if err == nil || c.token == "" {
|
||||
return err
|
||||
}
|
||||
msg := err.Error()
|
||||
if !strings.Contains(msg, c.token) {
|
||||
return err
|
||||
}
|
||||
return errors.New(strings.ReplaceAll(msg, c.token, "<redacted>"))
|
||||
}
|
||||
|
||||
func (c *Client) longPollRequestTimeout(timeoutSeconds int) time.Duration {
|
||||
telegramWait := time.Duration(timeoutSeconds) * time.Second
|
||||
if telegramWait < 0 {
|
||||
@@ -160,7 +176,10 @@ func (c *Client) postJSON(ctx context.Context, path string, payload any, target
|
||||
}
|
||||
res, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
// Transport errors are *url.Error and embed the full request URL,
|
||||
// which contains the bot token. Strip it before the error propagates
|
||||
// to logs.
|
||||
return c.redactToken(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -168,6 +169,28 @@ func TestSendTextRejectsNonSuccessHTTPStatusEvenWhenEnvelopeOK(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransportErrorRedactsBotToken(t *testing.T) {
|
||||
const token = "123456:super-secret-token"
|
||||
// Port 0 is reserved and never listens, forcing a transport-level dial
|
||||
// error whose *url.Error message embeds the token-bearing URL.
|
||||
client := &Client{
|
||||
baseURL: "http://127.0.0.1:0/bot" + token,
|
||||
token: token,
|
||||
httpClient: &http.Client{},
|
||||
requestTimeout: time.Second,
|
||||
}
|
||||
err := client.SendText(context.Background(), 123, nil, "hello")
|
||||
if err == nil {
|
||||
t.Fatal("expected transport error, got nil")
|
||||
}
|
||||
if strings.Contains(err.Error(), token) {
|
||||
t.Fatalf("error leaked bot token: %v", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "<redacted>") {
|
||||
t.Fatalf("error missing redaction placeholder: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewClientUsesConfiguredTimeoutForOrdinaryRequests(t *testing.T) {
|
||||
client := NewClient("token", 10*time.Second)
|
||||
if client.requestTimeout != 10*time.Second {
|
||||
|
||||
Reference in new issue
Block a user