From 94d28c1b86f2bc629d180eb3b343dfca628ceb9d Mon Sep 17 00:00:00 2001 From: admin-mf Date: Thu, 7 May 2026 12:14:59 -0500 Subject: [PATCH] forward-port: bound search response size with LimitReader Re-applies the response-size limit from the deleted pkg/tools/web.go to upstream's pkg/tools/integration/web.go. Wraps io.ReadAll on each search provider's resp.Body with io.LimitReader against a named searchMaxResponseSize constant (2 MB) to prevent unbounded memory growth from hostile or buggy upstream search APIs. Also normalises three existing inline `1<<20` (1 MB) limits upstream had already applied to use the same named constant. Co-Authored-By: Claude Opus 4.7 (1M context) --- pkg/tools/integration/web.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/pkg/tools/integration/web.go b/pkg/tools/integration/web.go index 75821e40d..22a379d5b 100644 --- a/pkg/tools/integration/web.go +++ b/pkg/tools/integration/web.go @@ -34,6 +34,10 @@ const ( defaultMaxChars = 50000 maxRedirects = 5 + + // searchMaxResponseSize bounds in-memory reads of search-provider responses + // to prevent unbounded memory growth on hostile/buggy upstreams. + searchMaxResponseSize int64 = 2 << 20 // 2 MB ) // Pre-compiled regexes for HTML text extraction @@ -293,7 +297,7 @@ func (p *BraveSearchProvider) Search( continue } - body, err := io.ReadAll(resp.Body) + body, err := io.ReadAll(io.LimitReader(resp.Body, searchMaxResponseSize)) resp.Body.Close() if err != nil { @@ -413,7 +417,7 @@ func (p *TavilySearchProvider) Search( continue } - body, err := io.ReadAll(resp.Body) + body, err := io.ReadAll(io.LimitReader(resp.Body, searchMaxResponseSize)) resp.Body.Close() if err != nil { @@ -501,7 +505,7 @@ func (p *SogouSearchProvider) Search( return "", fmt.Errorf("request failed: %w", err) } - body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) + body, err := io.ReadAll(io.LimitReader(resp.Body, searchMaxResponseSize)) resp.Body.Close() if err != nil { return "", fmt.Errorf("failed to read response: %w", err) @@ -594,7 +598,7 @@ func (p *DuckDuckGoSearchProvider) Search( } defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) + body, err := io.ReadAll(io.LimitReader(resp.Body, searchMaxResponseSize)) if err != nil { return "", fmt.Errorf("failed to read response: %w", err) } @@ -742,7 +746,7 @@ func (p *PerplexitySearchProvider) Search( continue } - body, err := io.ReadAll(resp.Body) + body, err := io.ReadAll(io.LimitReader(resp.Body, searchMaxResponseSize)) resp.Body.Close() if err != nil { @@ -919,7 +923,7 @@ func (p *GLMSearchProvider) Search( } defer resp.Body.Close() - body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) + body, err := io.ReadAll(io.LimitReader(resp.Body, searchMaxResponseSize)) if err != nil { return "", fmt.Errorf("failed to read response: %w", err) } @@ -1015,7 +1019,7 @@ func (p *BaiduSearchProvider) Search( } defer resp.Body.Close() - body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) + body, err := io.ReadAll(io.LimitReader(resp.Body, searchMaxResponseSize)) if err != nil { return "", fmt.Errorf("failed to read response: %w", err) }