diff --git a/config/config.example.json b/config/config.example.json index 1c11cd42a..373f3337d 100644 --- a/config/config.example.json +++ b/config/config.example.json @@ -351,7 +351,8 @@ "search_engine": "search_std", "max_results": 5 }, - "fetch_limit_bytes": 10485760 + "fetch_limit_bytes": 10485760, + "allow_private_hosts": false }, "cron": { "enabled": true, diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index f20a56b9c..1b62ed929 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -117,6 +117,9 @@ func registerSharedTools( registry *AgentRegistry, provider providers.LLMProvider, ) { + // Apply global settings that affect tool behavior. + tools.SetAllowPrivateWebFetchHosts(cfg.Tools.Web.AllowPrivateHosts) + for _, agentID := range registry.ListAgentIDs() { agent, ok := registry.GetAgent(agentID) if !ok { diff --git a/pkg/config/config.go b/pkg/config/config.go index 190341224..3eccebb17 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -694,6 +694,9 @@ type WebToolsConfig struct { // For authenticated proxies, prefer HTTP_PROXY/HTTPS_PROXY env vars instead of embedding credentials in config. Proxy string `json:"proxy,omitempty" env:"PICOCLAW_TOOLS_WEB_PROXY"` FetchLimitBytes int64 `json:"fetch_limit_bytes,omitempty" env:"PICOCLAW_TOOLS_WEB_FETCH_LIMIT_BYTES"` + // AllowPrivateHosts controls whether web_fetch may connect to local/private IPs. + // Defaults to false to reduce SSRF exposure. + AllowPrivateHosts bool `json:"allow_private_hosts" env:"PICOCLAW_TOOLS_WEB_ALLOW_PRIVATE_HOSTS"` } type CronToolsConfig struct { diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index dc534d852..8f4405b35 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -412,6 +412,7 @@ func DefaultConfig() *Config { }, Proxy: "", FetchLimitBytes: 10 * 1024 * 1024, // 10MB by default + AllowPrivateHosts: false, Brave: BraveConfig{ Enabled: false, APIKey: "", diff --git a/pkg/tools/web.go b/pkg/tools/web.go index e5036d3a8..116fa05e3 100644 --- a/pkg/tools/web.go +++ b/pkg/tools/web.go @@ -788,6 +788,12 @@ func NewWebFetchTool(maxChars int, fetchLimitBytes int64) (*WebFetchTool, error) // This is false in normal runtime to reduce SSRF exposure, and tests can override it temporarily. var allowPrivateWebFetchHosts atomic.Bool +// SetAllowPrivateWebFetchHosts configures whether the web_fetch tool may access local/private IPs. +// This is normally false to reduce SSRF attack surface. +func SetAllowPrivateWebFetchHosts(allow bool) { + allowPrivateWebFetchHosts.Store(allow) +} + func NewWebFetchToolWithProxy(maxChars int, proxy string, fetchLimitBytes int64) (*WebFetchTool, error) { if maxChars <= 0 { maxChars = defaultMaxChars