security(tools): add response body size limits to search providers

Add 2 MB LimitReader to BraveSearch, Tavily, DuckDuckGo, and
Perplexity search providers to prevent memory exhaustion from
unexpectedly large responses. Matches existing pattern used by
GLMSearchProvider.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
admin-mf 2026-03-05 23:56:19 -06:00
parent 42e037aec1
commit 0e2c7d1abd

View file

@ -22,8 +22,9 @@ const (
perplexityTimeout = 30 * time.Second // Perplexity (LLM-based, slower) perplexityTimeout = 30 * time.Second // Perplexity (LLM-based, slower)
fetchTimeout = 60 * time.Second // WebFetchTool fetchTimeout = 60 * time.Second // WebFetchTool
defaultMaxChars = 50000 defaultMaxChars = 50000
maxRedirects = 5 maxRedirects = 5
searchMaxResponseSize int64 = 2 << 20 // 2 MB — limit for search provider responses
) )
// Pre-compiled regexes for HTML text extraction // Pre-compiled regexes for HTML text extraction
@ -104,7 +105,7 @@ func (p *BraveSearchProvider) Search(ctx context.Context, query string, count in
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(io.LimitReader(resp.Body, searchMaxResponseSize))
if err != nil { if err != nil {
return "", fmt.Errorf("failed to read response: %w", err) return "", fmt.Errorf("failed to read response: %w", err)
} }
@ -191,7 +192,7 @@ func (p *TavilySearchProvider) Search(ctx context.Context, query string, count i
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(io.LimitReader(resp.Body, searchMaxResponseSize))
if err != nil { if err != nil {
return "", fmt.Errorf("failed to read response: %w", err) return "", fmt.Errorf("failed to read response: %w", err)
} }
@ -253,7 +254,7 @@ func (p *DuckDuckGoSearchProvider) Search(ctx context.Context, query string, cou
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(io.LimitReader(resp.Body, searchMaxResponseSize))
if err != nil { if err != nil {
return "", fmt.Errorf("failed to read response: %w", err) return "", fmt.Errorf("failed to read response: %w", err)
} }
@ -367,7 +368,7 @@ func (p *PerplexitySearchProvider) Search(ctx context.Context, query string, cou
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(io.LimitReader(resp.Body, searchMaxResponseSize))
if err != nil { if err != nil {
return "", fmt.Errorf("failed to read response: %w", err) return "", fmt.Errorf("failed to read response: %w", err)
} }