diff --git a/tests/tools/test_browser_url_validation.py b/tests/tools/test_browser_url_validation.py
new file mode 100644
index 0000000000000..18e1a182e9212
--- /dev/null
+++ b/tests/tools/test_browser_url_validation.py
@@ -0,0 +1,48 @@
+"""Tests for browser_navigate URL scheme and SSRF validation."""
+
+import json
+from unittest.mock import patch
+
+from tools.browser_tool import browser_navigate
+
+
+class TestBrowserUrlSchemeValidation:
+ """browser_navigate must reject non-http(s) schemes."""
+
+ def test_file_scheme_blocked(self):
+ result = json.loads(browser_navigate("file:///etc/shadow"))
+ assert result["success"] is False
+ assert "scheme" in result["error"].lower()
+
+ def test_javascript_scheme_blocked(self):
+ result = json.loads(browser_navigate("javascript:alert(1)"))
+ assert result["success"] is False
+ assert "scheme" in result["error"].lower()
+
+ def test_data_scheme_blocked(self):
+ result = json.loads(browser_navigate("data:text/html,
pwned
"))
+ assert result["success"] is False
+ assert "scheme" in result["error"].lower()
+
+ def test_ftp_scheme_blocked(self):
+ result = json.loads(browser_navigate("ftp://evil.com/malware"))
+ assert result["success"] is False
+ assert "scheme" in result["error"].lower()
+
+ def test_http_scheme_allowed(self):
+ """http URLs should pass scheme check (may fail later on browser connect)."""
+ with patch("tools.browser_tool._run_browser_command",
+ return_value={"success": True, "data": {"title": "Test", "url": "http://example.com"}}):
+ with patch("tools.browser_tool.check_website_access", return_value=None):
+ with patch("tools.browser_tool._get_session_info", return_value={"_first_nav": False}):
+ result = json.loads(browser_navigate("http://example.com"))
+ assert result.get("success") is True or "error" not in result or "scheme" not in result.get("error", "")
+
+ def test_https_scheme_allowed(self):
+ """https URLs should pass scheme check."""
+ with patch("tools.browser_tool._run_browser_command",
+ return_value={"success": True, "data": {"title": "Test", "url": "https://example.com"}}):
+ with patch("tools.browser_tool.check_website_access", return_value=None):
+ with patch("tools.browser_tool._get_session_info", return_value={"_first_nav": False}):
+ result = json.loads(browser_navigate("https://example.com"))
+ assert "scheme" not in result.get("error", "")
diff --git a/tests/tools/test_website_policy.py b/tests/tools/test_website_policy.py
index 52618a1d63cb2..6aaec73e7a6c3 100644
--- a/tests/tools/test_website_policy.py
+++ b/tests/tools/test_website_policy.py
@@ -292,6 +292,8 @@ def test_check_website_access_blocks_scheme_less_urls(tmp_path):
def test_browser_navigate_returns_policy_block(monkeypatch):
from tools import browser_tool
+ # Allow the URL past SSRF check so the website policy check is reached
+ monkeypatch.setattr(browser_tool, "_is_browser_url_safe", lambda url: True)
monkeypatch.setattr(
browser_tool,
"check_website_access",
diff --git a/tools/browser_tool.py b/tools/browser_tool.py
index 5aea75f552095..22f268c795247 100644
--- a/tools/browser_tool.py
+++ b/tools/browser_tool.py
@@ -70,6 +70,11 @@
from tools.website_policy import check_website_access
except Exception:
check_website_access = lambda url: None # noqa: E731 — fail-open if policy module unavailable
+
+try:
+ from tools.url_safety import is_safe_url as _is_browser_url_safe
+except Exception:
+ _is_browser_url_safe = lambda url: True # noqa: E731 — fail-open if module unavailable
from tools.browser_providers.base import CloudBrowserProvider
from tools.browser_providers.browserbase import BrowserbaseProvider
from tools.browser_providers.browser_use import BrowserUseProvider
@@ -947,6 +952,22 @@ def browser_navigate(url: str, task_id: Optional[str] = None) -> str:
Returns:
JSON string with navigation result (includes stealth features info on first nav)
"""
+ # Restrict to http/https — block file://, javascript:, data:, etc.
+ from urllib.parse import urlparse as _urlparse
+ _parsed = _urlparse(url)
+ if _parsed.scheme not in ("http", "https", ""):
+ return json.dumps({
+ "success": False,
+ "error": f"Unsupported URL scheme '{_parsed.scheme}'. Only http and https are allowed.",
+ })
+
+ # SSRF protection — block private/internal addresses
+ if not _is_browser_url_safe(url):
+ return json.dumps({
+ "success": False,
+ "error": "Blocked: URL targets a private or internal network address.",
+ })
+
# Website policy check — block before navigating
blocked = check_website_access(url)
if blocked: