Files
hermes-agent/tests/agent/test_provider_media.py
teknium1 b44a481334 fix(video-gen): trust the operator-configured origin on the first download hop
The OpenRouter video content URL is built from OPENROUTER_BASE_URL, not from
a provider response, yet save_url ran the full SSRF check on it. An operator
pointing base_url at a LAN/loopback relay could submit and poll (raw
requests) but the final download was refused as SSRF unless they set
security.allow_private_urls — contradicting the issue scope ("operator-
configured endpoints out of scope") and the security.md sentence that the
operator's own base_url is unaffected.

save_url grows a `trusted_origin` flag (plumbed through save_url_video and
set only by OpenRouterVideoGenProvider._save_completed_video): the first hop
skips the private-address class check and uses a plain client, but the
cloud-metadata floor (is_always_blocked_url) still applies, auth headers
stay on hop 1 only, and every redirect target is re-validated in full so a
relay cannot bounce us to another internal address. Provider-returned result
URLs (fal, xai, image providers) keep the full guard — default is False.

security.md now states the precise scope: only the direct base_url hop is
exempt; result URLs from a LAN-hosted provider still need allow_private_urls.
2026-09-18 10:22:14 -07:00

165 lines
5.9 KiB
Python

from __future__ import annotations
import pytest
from agent import provider_media
def _save_video(url: str, **kwargs):
return provider_media.save_url(
"videos",
url,
prefix="provider-test",
timeout=30,
max_bytes=1024,
chunk_size=64,
content_types={"video/mp4": "mp4"},
url_extensions=("mp4",),
default_extension="mp4",
label="Video",
empty_error="empty: {url}",
**kwargs,
)
def _stub_fetch(monkeypatch, handler):
"""Route save_url's fetch through an httpx MockTransport and stub the URL
policy check — SSRF behavior has dedicated tests in test_save_url_image.py;
these pin the streaming/content-type/headers contract."""
import httpx
monkeypatch.setattr("tools.url_safety.is_safe_url", lambda url: True)
monkeypatch.setattr(
"tools.url_safety.create_ssrf_safe_client",
lambda **kw: httpx.Client(transport=httpx.MockTransport(handler), **kw),
)
def test_save_url_forwards_headers_and_streams_to_cache(monkeypatch, tmp_path):
monkeypatch.setenv("HERMES_HOME", str(tmp_path / ".hermes"))
calls = []
import httpx
def handler(request):
calls.append(request)
return httpx.Response(200, headers={"Content-Type": "video/mp4"}, content=b"clip")
_stub_fetch(monkeypatch, handler)
path = _save_video(
"https://api.example/videos/job/content",
headers={"Authorization": "Bearer test"},
require_known_content_type=True,
)
assert path.read_bytes() == b"clip"
assert path.suffix == ".mp4"
assert len(calls) == 1
assert str(calls[0].url) == "https://api.example/videos/job/content"
assert calls[0].headers["Authorization"] == "Bearer test"
def test_save_url_strict_content_type_rejects_non_video(monkeypatch, tmp_path):
monkeypatch.setenv("HERMES_HOME", str(tmp_path / ".hermes"))
import httpx
_stub_fetch(
monkeypatch,
lambda request: httpx.Response(200, headers={"Content-Type": "text/html"}, content=b"not a video"),
)
with pytest.raises(ValueError, match="unexpected Content-Type text/html"):
_save_video(
"https://api.example/videos/job/content",
require_known_content_type=True,
)
assert not list(provider_media.cache_dir("videos").iterdir())
def test_save_url_redirect_scopes_caller_headers_to_first_hop_and_fails_closed(monkeypatch, tmp_path):
"""Caller auth headers (provider base_url fetches) go to the first hop only — a
redirect target must never receive them — and a 3xx without ``Location`` is an
error, never a cached body."""
monkeypatch.setenv("HERMES_HOME", str(tmp_path / ".hermes"))
calls = []
import httpx
def handler(request):
calls.append(request)
if request.url.path == "/start":
return httpx.Response(302, headers={"Location": "/final"})
if request.url.path == "/no-location":
return httpx.Response(302, content=b"<html>3xx body</html>")
return httpx.Response(200, headers={"Content-Type": "video/mp4"}, content=b"clip")
_stub_fetch(monkeypatch, handler)
path = _save_video(
"https://api.example/start",
headers={"Authorization": "Bearer test"},
require_known_content_type=True,
)
assert path.read_bytes() == b"clip"
assert len(calls) == 2
assert calls[0].headers.get("Authorization") == "Bearer test"
assert calls[1].headers.get("Authorization") is None
with pytest.raises(ValueError, match="without a Location"):
_save_video("https://api.example/no-location", require_known_content_type=True)
assert list(provider_media.cache_dir("videos").iterdir()) == [path]
def test_save_url_trusted_origin_skips_private_check_on_first_hop_only(monkeypatch, tmp_path):
"""``trusted_origin=True`` (the caller built the URL from the operator's own
provider ``base_url``) must let a LAN/loopback relay serve the first hop, but the
cloud-metadata floor still applies and every redirect target is re-validated in
full — a relay cannot bounce us to another internal address."""
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
monkeypatch.setenv("HERMES_HOME", str(tmp_path / ".hermes"))
monkeypatch.delenv("HERMES_ALLOW_PRIVATE_URLS", raising=False)
hits = []
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
hits.append((self.path, self.headers.get("Authorization")))
if self.path == "/bounce":
self.send_response(302)
self.send_header("Location", "/content")
self.end_headers()
return
self.send_response(200)
self.send_header("Content-Type", "video/mp4")
self.end_headers()
self.wfile.write(b"clip")
def log_message(self, *_):
pass
server = HTTPServer(("127.0.0.1", 0), Handler)
threading.Thread(target=server.serve_forever, daemon=True).start()
try:
base = f"http://127.0.0.1:{server.server_port}"
path = _save_video(f"{base}/content", headers={"Authorization": "Bearer test"},
require_known_content_type=True, trusted_origin=True)
assert path.read_bytes() == b"clip"
assert hits == [("/content", "Bearer test")]
with pytest.raises(ValueError, match="SSRF safety check"):
_save_video(f"{base}/bounce", headers={"Authorization": "Bearer test"},
require_known_content_type=True, trusted_origin=True)
assert hits[1:] == [("/bounce", "Bearer test")] # hop 2 (loopback) refused before connecting
with pytest.raises(ValueError, match="always-blocked"):
_save_video("http://169.254.169.254/latest/meta-data", trusted_origin=True)
assert len(hits) == 2
finally:
server.shutdown()
server.server_close()