Panniantong/Agent-Reach · error · TranscribeError

SSRF blocked: internal host is not allowed

Error message

SSRF blocked: internal host is not allowed

What it means

Raised by _assert_safe_public_url (transcribe.py:244-245) when the canonicalized host is in _BLOCKED_HOSTS ({'localhost', 'metadata.google.internal'}) or ends with '.localhost'. These names resolve to the local machine or the cloud metadata service, so fetching them is exactly what the SSRF guard exists to prevent.

Source

Thrown at agent_reach/transcribe.py:245

        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")
    try:
        host = raw_host.encode("idna").decode("ascii").lower().rstrip(".")
    except UnicodeError:
        raise TranscribeError("SSRF blocked: URL host is invalid") from None
    if host in _BLOCKED_HOSTS or host.endswith(".localhost"):
        raise TranscribeError("SSRF blocked: internal host is not allowed")
    if _is_private_ip(host):
        raise TranscribeError("SSRF blocked: private/internal IP is not allowed")


def download_audio(url: str, out_dir: Path) -> Path:
    """Download audio with yt-dlp into out_dir; return the resulting file path."""
    _assert_safe_public_url(url)
    _require("yt-dlp")
    template = out_dir / "source.%(ext)s"
    _run(
        [
            "yt-dlp",
            "-x",
            "--audio-format",
            "m4a",
            "--audio-quality",
            "0",
            "--no-playlist",

View on GitHub (pinned to 93ae1d18c3)

Solutions

  1. Serve the file from a publicly reachable host or pass the local file path directly to transcribe()
  2. If you control the media server, bind it to a real (non-localhost-resolving) hostname reachable from the machine
  3. Never feed agent-supplied URLs to this API without your own allowlist — the blocklist is deliberate but minimal

Example fix

# before
download_audio("http://localhost:8080/ep.m4a", out_dir)  # SSRF blocked: internal host is not allowed

# after: local file takes the direct path
text = transcribe("/srv/media/ep.m4a")
Defensive patterns

Strategy: validation

Validate before calling

_BLOCKED = {"localhost", "metadata.google.internal"}

def host_not_internal(host: str) -> bool:
    h = host.strip().rstrip(".").lower()
    return h not in _BLOCKED and not h.endswith(".localhost")

Try / catch

from agent_reach.transcribe import TranscribeError
try:
    transcribe(url)
except TranscribeError as e:
    if "internal host" in str(e):
        serve_locally_or_reject(url)  # never 'fix' by bypassing the guard
    raise

Prevention

When it happens

Trigger: download_audio('http://localhost:8080/recording.m4a'), 'https://api.localhost/x', 'http://metadata.google.internal/computeMetadata/v1/...'. Dot-suffixed variants ('localhost.') are normalized first, so trailing-dot evasion also fails.

Common situations: Local e2e tests pointing the transcription pipeline at a dev server; agents on cloud VMs being tricked into reading the metadata endpoint (the attack this blocks); misconfigured env vars that default a media base URL to localhost.

Related errors


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