Panniantong/Agent-Reach · error · TranscribeError

SSRF blocked: only public http(s) URLs are allowed

Error message

SSRF blocked: only public http(s) URLs are allowed

What it means

Raised by _assert_safe_public_url (transcribe.py:216-221) during SSRF validation when the URL has no '://' scheme and the text before the first slash contains a colon whose part after the last colon is not pure digits — i.e. it looks like a scheme or malformed port rather than host:port. Scheme-less input is auto-prefixed with https:// only when it plausibly is a bare host[:port].

Source

Thrown at agent_reach/transcribe.py:221

        (
            ip.is_private,
            ip.is_loopback,
            ip.is_link_local,
            ip.is_reserved,
            ip.is_multicast,
            ip.is_unspecified,
        )
    )


def _assert_safe_public_url(url: str) -> None:
    """Reject literal local/internal URLs without DNS-resolving public hosts."""
    if "://" not in url:
        before_slash = url.split("/", 1)[0]
        if ":" in before_slash:
            host_part, port_part = before_slash.rsplit(":", 1)
            if not host_part or not port_part.isdigit():
                raise TranscribeError("SSRF blocked: only public http(s) URLs are allowed")
        normalized_url = f"https://{url}"
        parsed = urlparse(normalized_url)
    else:
        normalized_url = url
        parsed = urlparse(url)
        if parsed.scheme not in {"http", "https"}:
            raise TranscribeError("SSRF blocked: only public http(s) URLs are allowed")

    raw_authority = normalized_url.split("://", 1)[1]
    raw_authority = raw_authority.split("/", 1)[0]
    raw_authority = raw_authority.split("?", 1)[0]
    raw_authority = raw_authority.split("#", 1)[0]
    if "\\" in raw_authority or "%" in raw_authority:
        raise TranscribeError("SSRF blocked: encoded or ambiguous URL host")

    raw_host = (parsed.hostname or "").strip().rstrip(".")
    if not raw_host:
        raise TranscribeError("SSRF blocked: URL host is missing")

View on GitHub (pinned to 93ae1d18c3)

Solutions

  1. Pass a full http(s) URL including scheme: 'https://example.com/file.mp3'
  2. For bare hosts, use host or host:digits form ('example.com:8443') which normalizes correctly
  3. For rtsp/ftp/other-protocol sources, download the media yourself first, then pass the local file path to transcribe()

Example fix

# before
download_audio("rtsp:camera-stream", out_dir)  # SSRF blocked

# after: fetch out-of-band, hand over a local file
subprocess.run(["ffmpeg", "-rtsp_transport", "tcp", "-i", "rtsp://cam/stream", "-t", "60", "cam.m4a"], check=True)
transcribe("cam.m4a")
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlparse

def is_http_url(url: str) -> bool:
    if "://" in url:
        return urlparse(url).scheme in {"http", "https"}
    before_slash = url.split("/", 1)[0]
    if ":" in before_slash:
        _, port = before_slash.rsplit(":", 1)
        return port.isdigit()
    return True

Try / catch

from agent_reach.transcribe import TranscribeError
try:
    transcribe(source)
except TranscribeError as e:
    if str(e).startswith("SSRF blocked"):
        reject_user_input(source)  # do not retry; fix the URL
    raise

Prevention

When it happens

Trigger: download_audio('ftp://...') goes to the scheme branch (error 64); this specific raise fires for inputs like 'example.com:notaport', 'rtsp:media', 'mailto:x', or a bare 'https:' fragment without '//'. Any scheme-less string whose colon suffix is non-numeric is rejected.

Common situations: Passing a URI copied with a custom scheme (rtsp:, rtmp:) or a mangled copy-paste like 'example.com:8080:extra'. Developers testing download_audio() with non-HTTP media URLs (RTSP cameras, FTP archives).

Related errors


AI-assisted analysis of Panniantong/Agent-Reach@93ae1d18c3 (2026-08-14). Data as JSON: /api/errors/3826560d35d46e2c. Report an issue: GitHub.