merge: sync with upstream NousResearch/hermes-agent (499 commits)
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 0s

Resolves all 10 conflicts by keeping upstream versions of core files.
Our 142 unique additions (wizard-bootstrap, CI, docs, tests, agent evolution) preserved.

Upstream highlights:
- Browser Use replaces Browserbase
- notify_on_complete for background processes
- Permanent command allowlist for approvals
- Reasoning block display fixes
- Credential pool auto-detection
- Many bug fixes and improvements
This commit is contained in:
Alexander Whitestone
2026-04-07 10:00:16 -04:00
160 changed files with 40216 additions and 279 deletions

View File

@@ -181,6 +181,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:
@@ -202,6 +205,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()