Compare commits
2 Commits
security/f
...
security/f
| Author | SHA1 | Date | |
|---|---|---|---|
| cfaf6c827e | |||
| cf1afb07f2 |
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user