mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 00:16:38 +00:00
fix(telegram): enforce transport policy and SSRF guard for media downloads
* fix(telegram): thread transport policy into media downloads with SSRF guard * fix(telegram): trust configured APIServer during media downloads * fix(telegram): use proper download timeout and clone DefaultTransport - Clone http.DefaultTransport when proxy is configured to preserve connection pool, TLS handshake timeout, and keep-alive defaults - Use dedicated 5-minute context timeout for media downloads instead of the shared 30s client timeout, preventing large file timeouts (local Bot API supports up to 200 MB) --------- Co-authored-by: viettranx <viettranx@gmail.com>
This commit is contained in:
1 parent
31ff5eefca
commit
d46061405f
2 files changed
+37
-6
No files matched your search
@@ -23,6 +23,7 @@ type Channel struct {
|
||||
*channels.BaseChannel
|
||||
bot *telego.Bot
|
||||
config config.TelegramConfig
|
||||
httpClient *http.Client
|
||||
pairingService store.PairingStore
|
||||
agentStore store.AgentStore // for group file writer management (nil if not configured)
|
||||
teamStore store.TeamStore // for /tasks, /task_detail commands (nil if not configured)
|
||||
@@ -61,17 +62,20 @@ func New(cfg config.TelegramConfig, msgBus *bus.MessageBus, pairingSvc store.Pai
|
||||
opts = append(opts, telego.WithAPIServer(cfg.APIServer))
|
||||
}
|
||||
|
||||
httpClient := &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
if cfg.Proxy != "" {
|
||||
proxyURL, parseErr := url.Parse(cfg.Proxy)
|
||||
if parseErr != nil {
|
||||
return nil, fmt.Errorf("invalid proxy URL %q: %w", cfg.Proxy, parseErr)
|
||||
}
|
||||
opts = append(opts, telego.WithHTTPClient(&http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyURL(proxyURL),
|
||||
},
|
||||
}))
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
transport.Proxy = http.ProxyURL(proxyURL)
|
||||
httpClient.Transport = transport
|
||||
}
|
||||
opts = append(opts, telego.WithHTTPClient(httpClient))
|
||||
|
||||
bot, err := telego.NewBot(cfg.Token, opts...)
|
||||
if err != nil {
|
||||
@@ -95,6 +99,7 @@ func New(cfg config.TelegramConfig, msgBus *bus.MessageBus, pairingSvc store.Pai
|
||||
BaseChannel: base,
|
||||
bot: bot,
|
||||
config: cfg,
|
||||
httpClient: httpClient,
|
||||
pairingService: pairingSvc,
|
||||
agentStore: agentStore,
|
||||
teamStore: teamStore,
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/mymmrac/telego"
|
||||
|
||||
"github.com/nextlevelbuilder/goclaw/internal/channels/media"
|
||||
"github.com/nextlevelbuilder/goclaw/internal/tools"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -227,7 +228,32 @@ func (c *Channel) downloadMedia(ctx context.Context, fileID string, maxBytes int
|
||||
downloadURL = fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", c.config.Token, file.FilePath)
|
||||
}
|
||||
|
||||
resp, err := http.Get(downloadURL)
|
||||
// SSRF Protection: check the resolved URL before connecting.
|
||||
// We skip the check IF the host is our explicitly configured (trusted) API server.
|
||||
isTrusted := c.config.APIServer != "" && strings.HasPrefix(downloadURL, c.config.APIServer)
|
||||
if !isTrusted {
|
||||
if err := tools.CheckSSRF(downloadURL); err != nil {
|
||||
return "", fmt.Errorf("SSRF protection: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Use a generous timeout for media downloads (large files via local Bot API
|
||||
// can be up to 200 MB). The shared httpClient has a 30s timeout suited for
|
||||
// API calls, so we override per-request with a dedicated context.
|
||||
dlCtx, dlCancel := context.WithTimeout(ctx, 5*time.Minute)
|
||||
defer dlCancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(dlCtx, "GET", downloadURL, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("create download request: %w", err)
|
||||
}
|
||||
|
||||
// Clone the shared client without the 30s Timeout so the per-request
|
||||
// context (5 min) governs the download duration instead.
|
||||
dlClient := *c.httpClient
|
||||
dlClient.Timeout = 0
|
||||
|
||||
resp, err := dlClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("download file: %w", err)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user