c2e11bf418
_download_url_with_cap called urlopen() after only a scheme check, so a model-controlled URL could reach loopback services, RFC1918/CGNAT hosts, or cloud metadata endpoints (169.254.169.254), and a public host could 302 to any of those unvalidated. Route the fetch through the repo's canonical SSRF guard instead: validate every hop with tools.url_safety.is_safe_url() and follow redirects manually (httpx, follow_redirects=False, 5-hop limit) so each Location target is re-checked before it is fetched — the same pattern as tools/skills_hub._guarded_http_get. The streaming size cap is unchanged. Local-fixture tests opt in via HERMES_ALLOW_PRIVATE_URLS (the guard's documented escape hatch); new tests pin rejection of loopback, cloud-metadata, and private-range URLs, a mocked public→loopback redirect, and a mocked public happy path.