From cfaf6c827e31fa6b17664e6df8611d88bcab18fb Mon Sep 17 00:00:00 2001 From: Allegro Date: Mon, 30 Mar 2026 23:57:22 +0000 Subject: [PATCH] security: validate CDP URLs to prevent SSRF (V-010, CVSS 8.4) Add URL validation before fetching Chrome DevTools Protocol endpoints. Only allows localhost and private network addresses. Changes: - tools/browser_tool.py: Add hostname validation in _resolve_cdp_override() - Block external URLs to prevent SSRF attacks - Log security errors for rejected URLs CVSS: 8.4 (High) Refs: V-010 in SECURITY_AUDIT_REPORT.md CWE-918: Server-Side Request Forgery --- tools/browser_tool.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tools/browser_tool.py b/tools/browser_tool.py index ffb772c1d..b58f388c1 100644 --- a/tools/browser_tool.py +++ b/tools/browser_tool.py @@ -170,6 +170,9 @@ def _resolve_cdp_override(cdp_url: str) -> str: For discovery-style endpoints we fetch /json/version and return the webSocketDebuggerUrl so downstream tools always receive a concrete browser websocket instead of an ambiguous host:port URL. + + SECURITY FIX (V-010): Validates URLs before fetching to prevent SSRF. + Only allows localhost/private network addresses for CDP connections. """ raw = (cdp_url or "").strip() if not raw: @@ -191,6 +194,35 @@ def _resolve_cdp_override(cdp_url: str) -> str: else: version_url = discovery_url.rstrip("/") + "/json/version" + # SECURITY FIX (V-010): Validate URL before fetching + # Only allow localhost and private networks for CDP + from urllib.parse import urlparse + parsed = urlparse(version_url) + hostname = parsed.hostname or "" + + # Allow only safe hostnames for CDP + allowed_hostnames = ["localhost", "127.0.0.1", "0.0.0.0", "::1"] + if hostname not in allowed_hostnames: + # Check if it's a private IP + try: + import ipaddress + ip = ipaddress.ip_address(hostname) + if not (ip.is_private or ip.is_loopback): + logger.error( + "SECURITY: Rejecting CDP URL '%s' - only localhost and private " + "networks are allowed to prevent SSRF attacks.", + raw + ) + return raw # Return original without fetching + except ValueError: + # Not an IP - reject unknown hostnames + logger.error( + "SECURITY: Rejecting CDP URL '%s' - unknown hostname '%s'. " + "Only localhost and private IPs are allowed.", + raw, hostname + ) + return raw + try: response = requests.get(version_url, timeout=10) response.raise_for_status() -- 2.43.0