fix(tools): tighten web fetch URL cleaning
Made-with: Cursor
This commit is contained in:
@@ -388,9 +388,7 @@ class WebFetchTool(Tool):
|
|||||||
max_chars: int | None = None,
|
max_chars: int | None = None,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
url = url.strip().strip("`").strip('"').strip("'")
|
url = url.strip(" \t\r\n`\"'")
|
||||||
if not (url.startswith("http://") or url.startswith("https://")):
|
|
||||||
return json.dumps({"error": "Invalid URL after cleaning", "url": url}, ensure_ascii=False)
|
|
||||||
extract_mode = kwargs.pop("extractMode", extract_mode)
|
extract_mode = kwargs.pop("extractMode", extract_mode)
|
||||||
max_chars = kwargs.pop("maxChars", max_chars) or self.max_chars
|
max_chars = kwargs.pop("maxChars", max_chars) or self.max_chars
|
||||||
is_valid, error_msg = _validate_url_safe(url)
|
is_valid, error_msg = _validate_url_safe(url)
|
||||||
|
|||||||
@@ -110,6 +110,24 @@ async def test_execute_strips_space_and_backticks():
|
|||||||
assert "error" not in data, f"unexpected error: {data}"
|
assert "error" not in data, f"unexpected error: {data}"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_execute_strips_mixed_markdown_and_quotes():
|
||||||
|
tool = WebFetchTool()
|
||||||
|
with _patch_env()[0], _patch_env()[1]:
|
||||||
|
result = await tool.execute(url='"`https://example.com/page`"')
|
||||||
|
data = json.loads(result)
|
||||||
|
assert "error" not in data, f"unexpected error: {data}"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_execute_keeps_case_insensitive_http_scheme():
|
||||||
|
tool = WebFetchTool()
|
||||||
|
with _patch_env()[0], _patch_env()[1]:
|
||||||
|
result = await tool.execute(url="HTTPS://example.com/page")
|
||||||
|
data = json.loads(result)
|
||||||
|
assert "error" not in data, f"unexpected error: {data}"
|
||||||
|
|
||||||
|
|
||||||
# --- startswith guard tests ---
|
# --- startswith guard tests ---
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -118,7 +136,7 @@ async def test_execute_rejects_non_http_url_after_cleaning():
|
|||||||
result = await tool.execute(url="ftp://example.com/file")
|
result = await tool.execute(url="ftp://example.com/file")
|
||||||
data = json.loads(result)
|
data = json.loads(result)
|
||||||
assert "error" in data
|
assert "error" in data
|
||||||
assert "Invalid URL" in data["error"]
|
assert "URL validation failed" in data["error"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -127,7 +145,7 @@ async def test_execute_rejects_garbage_after_cleaning():
|
|||||||
result = await tool.execute(url="`not a url at all`")
|
result = await tool.execute(url="`not a url at all`")
|
||||||
data = json.loads(result)
|
data = json.loads(result)
|
||||||
assert "error" in data
|
assert "error" in data
|
||||||
assert "Invalid URL" in data["error"]
|
assert "URL validation failed" in data["error"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -136,4 +154,4 @@ async def test_execute_rejects_bare_domain_after_cleaning():
|
|||||||
result = await tool.execute(url="`example.com/page`")
|
result = await tool.execute(url="`example.com/page`")
|
||||||
data = json.loads(result)
|
data = json.loads(result)
|
||||||
assert "error" in data
|
assert "error" in data
|
||||||
assert "Invalid URL" in data["error"]
|
assert "URL validation failed" in data["error"]
|
||||||
|
|||||||
Reference in New Issue
Block a user