65f5caf4d9
Co-authored-by: Cursor <cursoragent@cursor.com>
94 lines
2.5 KiB
Python
94 lines
2.5 KiB
Python
import ipaddress
|
|
import os
|
|
import re
|
|
from urllib.parse import urlparse
|
|
|
|
|
|
BLOCKED_HOSTNAMES = {
|
|
"localhost",
|
|
"localhost.localdomain",
|
|
"metadata.google.internal",
|
|
}
|
|
|
|
PRIVATE_NETWORKS = [
|
|
ipaddress.ip_network("0.0.0.0/8"),
|
|
ipaddress.ip_network("10.0.0.0/8"),
|
|
ipaddress.ip_network("127.0.0.0/8"),
|
|
ipaddress.ip_network("169.254.0.0/16"),
|
|
ipaddress.ip_network("172.16.0.0/12"),
|
|
ipaddress.ip_network("192.168.0.0/16"),
|
|
ipaddress.ip_network("::1/128"),
|
|
ipaddress.ip_network("fc00::/7"),
|
|
ipaddress.ip_network("fe80::/10"),
|
|
]
|
|
|
|
ALLOWED_SCHEMES = {"http", "https"}
|
|
|
|
|
|
class SecurityError(ValueError):
|
|
pass
|
|
|
|
|
|
def _normalize_url(raw_url: str) -> str:
|
|
url = raw_url.strip()
|
|
if not url:
|
|
raise SecurityError("URL 不能为空")
|
|
|
|
if not re.match(r"^[a-zA-Z][a-zA-Z0-9+.-]*://", url):
|
|
url = f"https://{url}"
|
|
|
|
parsed = urlparse(url)
|
|
if parsed.scheme not in ALLOWED_SCHEMES:
|
|
raise SecurityError("仅允许 http/https 协议")
|
|
|
|
if not parsed.netloc:
|
|
raise SecurityError("URL 格式无效")
|
|
|
|
if parsed.username or parsed.password:
|
|
raise SecurityError("URL 中不允许包含用户名或密码")
|
|
|
|
hostname = parsed.hostname
|
|
if not hostname:
|
|
raise SecurityError("无法解析主机名")
|
|
|
|
hostname_lower = hostname.lower()
|
|
if hostname_lower in BLOCKED_HOSTNAMES:
|
|
raise SecurityError("不允许访问该主机")
|
|
|
|
if hostname_lower.endswith(".local") or hostname_lower.endswith(".internal"):
|
|
raise SecurityError("不允许访问内网域名")
|
|
|
|
try:
|
|
ip = ipaddress.ip_address(hostname)
|
|
except ValueError:
|
|
return url
|
|
|
|
for network in PRIVATE_NETWORKS:
|
|
if ip in network:
|
|
raise SecurityError("不允许访问内网或本地地址")
|
|
|
|
return url
|
|
|
|
|
|
def validate_url(raw_url: str) -> str:
|
|
return _normalize_url(raw_url)
|
|
|
|
|
|
def get_max_sessions() -> int:
|
|
return max(1, int(os.getenv("MAX_SESSIONS", "1")))
|
|
|
|
|
|
def get_idle_timeout() -> int:
|
|
return max(60, int(os.getenv("SESSION_IDLE_TIMEOUT", "1800")))
|
|
|
|
|
|
def get_viewport_size() -> tuple[int, int]:
|
|
width = max(800, int(os.getenv("VIEWPORT_WIDTH", "1280")))
|
|
height = max(600, int(os.getenv("VIEWPORT_HEIGHT", "720")))
|
|
return width, height
|
|
|
|
|
|
def get_screencast_quality() -> int:
|
|
quality = int(os.getenv("SCREENCAST_QUALITY", "80"))
|
|
return min(100, max(10, quality))
|