Skip to content

Commit fea875e

Browse files
feat(core): apply hardcoded SSRF filters to WebFetchTool
WebFetchTool now checks URLs against compiled deny filters before fetching. Hardcoded patterns block localhost, private IPs (10.x, 172.16-31.x, 192.168.x, 127.x), metadata endpoints, file:// and [::1]. User config under [tools.filters.web_fetch] can extend but never remove these defaults via merge_hardcoded().
1 parent 5fa1922 commit fea875e

1 file changed

Lines changed: 25 additions & 2 deletions

File tree

  • crates/core/src/agent/tools

crates/core/src/agent/tools/mod.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ pub fn create_safe_tools(
4040
config: &Config,
4141
memory: Option<Arc<MemoryManager>>,
4242
) -> Result<Vec<Box<dyn Tool>>> {
43+
use super::hardcoded_filters;
44+
use super::tool_filters::CompiledToolFilter;
45+
4346
let workspace = config.workspace_path();
4447

4548
// Use indexed memory search if MemoryManager is provided, otherwise fallback to grep-based
@@ -49,10 +52,25 @@ pub fn create_safe_tools(
4952
Box::new(MemorySearchTool::new(workspace.clone()))
5053
};
5154

55+
// Compile web_fetch filter with SSRF deny patterns
56+
let web_fetch_filter = config
57+
.tools
58+
.filters
59+
.get("web_fetch")
60+
.map(CompiledToolFilter::compile)
61+
.unwrap_or_else(|| Ok(CompiledToolFilter::permissive()))?
62+
.merge_hardcoded(
63+
hardcoded_filters::WEB_FETCH_DENY_SUBSTRINGS,
64+
hardcoded_filters::WEB_FETCH_DENY_PATTERNS,
65+
)?;
66+
5267
let mut tools: Vec<Box<dyn Tool>> = vec![
5368
memory_search_tool,
5469
Box::new(MemoryGetTool::new(workspace)),
55-
Box::new(WebFetchTool::new(config.tools.web_fetch_max_bytes)),
70+
Box::new(WebFetchTool::new(
71+
config.tools.web_fetch_max_bytes,
72+
web_fetch_filter,
73+
)),
5674
];
5775

5876
// Conditionally add web search tool
@@ -477,13 +495,15 @@ fn extract_readable_text(html: &str, url: &reqwest::Url) -> String {
477495
pub struct WebFetchTool {
478496
client: reqwest::Client,
479497
max_bytes: usize,
498+
filter: super::tool_filters::CompiledToolFilter,
480499
}
481500

482501
impl WebFetchTool {
483-
pub fn new(max_bytes: usize) -> Self {
502+
pub fn new(max_bytes: usize, filter: super::tool_filters::CompiledToolFilter) -> Self {
484503
Self {
485504
client: reqwest::Client::new(),
486505
max_bytes,
506+
filter,
487507
}
488508
}
489509
}
@@ -517,6 +537,9 @@ impl Tool for WebFetchTool {
517537
.as_str()
518538
.ok_or_else(|| anyhow::anyhow!("Missing url"))?;
519539

540+
// Check URL against SSRF deny filters (fast, static patterns)
541+
self.filter.check(url, "web_fetch", "url")?;
542+
520543
let parsed_url = validate_web_fetch_url(url).await?;
521544
debug!("Fetching URL: {}", parsed_url);
522545

0 commit comments

Comments
 (0)