diff --git a/nanobot/agent/tools/web.py b/nanobot/agent/tools/web.py index e25e196c..6378a797 100644 --- a/nanobot/agent/tools/web.py +++ b/nanobot/agent/tools/web.py @@ -388,9 +388,7 @@ class WebFetchTool(Tool): max_chars: int | None = None, **kwargs: Any, ) -> Any: - url = url.strip().strip("`").strip('"').strip("'") - if not (url.startswith("http://") or url.startswith("https://")): - return json.dumps({"error": "Invalid URL after cleaning", "url": url}, ensure_ascii=False) + url = url.strip(" \t\r\n`\"'") extract_mode = kwargs.pop("extractMode", extract_mode) max_chars = kwargs.pop("maxChars", max_chars) or self.max_chars is_valid, error_msg = _validate_url_safe(url) diff --git a/tests/tools/test_web_fetch_url_sanitization.py b/tests/tools/test_web_fetch_url_sanitization.py index e44b206a..8a24338c 100644 --- a/tests/tools/test_web_fetch_url_sanitization.py +++ b/tests/tools/test_web_fetch_url_sanitization.py @@ -110,6 +110,24 @@ async def test_execute_strips_space_and_backticks(): 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 --- @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") data = json.loads(result) assert "error" in data - assert "Invalid URL" in data["error"] + assert "URL validation failed" in data["error"] @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`") data = json.loads(result) assert "error" in data - assert "Invalid URL" in data["error"] + assert "URL validation failed" in data["error"] @pytest.mark.asyncio @@ -136,4 +154,4 @@ async def test_execute_rejects_bare_domain_after_cleaning(): result = await tool.execute(url="`example.com/page`") data = json.loads(result) assert "error" in data - assert "Invalid URL" in data["error"] + assert "URL validation failed" in data["error"]