Files
Nexus/browser-worker/browser_url_safe.py
T
Nexus Agent e833b923c8 feat(browser): 远程浏览器 Worker 桥接与顶栏固定悬浮窗
接入 Playwright Worker、会话 WebSocket 与全局浏览器面板(固定于 App Bar 下);
含验证码 stealth、设置项默认音与 URL 安全校验;附带 worker 部署与设计文档。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 06:49:20 +08:00

78 lines
2.5 KiB
Python

"""SSRF-safe URL validation (Worker copy — keep in sync with server/utils/browser_url_safe.py)."""
from __future__ import annotations
import ipaddress
import socket
from urllib.parse import urlparse
BLOCKED_HOSTNAMES = frozenset({
"localhost",
"localhost.localdomain",
"metadata",
"metadata.google.internal",
"169.254.169.254",
})
def _blocked_ip(ip: ipaddress.IPv4Address | ipaddress.IPv6Address) -> bool:
if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved or ip.is_multicast:
return True
if isinstance(ip, ipaddress.IPv4Address):
if ip in ipaddress.ip_network("0.0.0.0/8"):
return True
if ip in ipaddress.ip_network("100.64.0.0/10"):
return True
if isinstance(ip, ipaddress.IPv6Address):
if ip == ipaddress.IPv6Address("::1"):
return True
if ip in ipaddress.ip_network("fc00::/7"):
return True
if ip in ipaddress.ip_network("fe80::/10"):
return True
return False
def resolve_host_blocked(hostname: str) -> str | None:
host = hostname.lower().strip(".")
if not host:
return "Empty hostname"
if host in BLOCKED_HOSTNAMES or "metadata" in host:
return f"Blocked hostname: {hostname}"
try:
infos = socket.getaddrinfo(host, None, type=socket.SOCK_STREAM)
except socket.gaierror as exc:
return f"DNS resolution failed: {exc}"
if not infos:
return f"No addresses for hostname: {hostname}"
for info in infos:
ip_str = info[4][0]
try:
ip = ipaddress.ip_address(ip_str)
except ValueError:
return f"Invalid IP: {ip_str}"
if _blocked_ip(ip):
return f"Blocked IP: {ip_str}"
return None
def validate_navigation_url(url: str) -> tuple[str | None, str | None]:
trimmed = url.strip()
if not trimmed:
return None, "Empty URL"
try:
with_proto = trimmed if "://" in trimmed else f"https://{trimmed}"
parsed = urlparse(with_proto)
except Exception:
return None, "Invalid URL"
if parsed.scheme not in ("http", "https"):
return None, f"Unsupported scheme: {parsed.scheme}"
if parsed.username or parsed.password:
return None, "Credentials in URL are not allowed"
if not parsed.hostname:
return None, "Missing hostname"
host_err = resolve_host_blocked(parsed.hostname)
if host_err:
return None, host_err
return parsed.geturl(), None