Panniantong/Agent-Reach · error · TranscribeError

SSRF blocked: URL host is invalid

Error message

SSRF blocked: URL host is invalid

What it means

Raised by _assert_safe_public_url (transcribe.py:240-243) when the extracted hostname fails IDNA encoding (raw_host.encode('idna') raises UnicodeError). Hosts with disallowed Unicode codepoints, empty labels, or overlong labels cannot be safely canonicalized, so the guard treats them as invalid rather than passing ambiguous bytes to yt-dlp.

Source

Thrown at agent_reach/transcribe.py:243

        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")
    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",

View on GitHub (pinned to 93ae1d18c3)

Solutions

  1. Clean the host: strip zero-width/whitespace characters, normalize to NFC, replace typographic dashes with '-'
  2. Use the punycode form of IDN hosts (e.g. 'xn--bcher-kva.example' instead of 'bücher.example' if encoding keeps failing)
  3. Validate hosts against a strict regex (^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$) before calling transcribe()

Example fix

# before
url = "https://exa​mple.com/audio.mp3"  # zero-width space -> SSRF blocked: URL host is invalid

# after
import unicodedata
host = unicodedata.normalize("NFKC", host).replace("\u200b", "").replace("\u2013", "-")
url = f"https://{host}/audio.mp3"
Defensive patterns

Strategy: validation

Validate before calling

import unicodedata

def host_encodes_idna(host: str) -> bool:
    host = host.strip().rstrip(".")
    try:
        host.encode("idna")
        return True
    except UnicodeError:
        return False

def clean_host(host: str) -> str:
    return (unicodedata.normalize("NFKC", host)
            .replace("\u200b", "").replace("\u2013", "-").replace(" ", ""))

Try / catch

from agent_reach.transcribe import TranscribeError
try:
    transcribe(url)
except TranscribeError as e:
    if "URL host is invalid" in str(e):
        return transcribe(url_with_cleaned_host(url))  # NFKC-normalize, strip invisibles
    raise

Prevention

When it happens

Trigger: Hostnames containing characters IDNA rejects: 'https://exa mple.com/' (space), 'https://exa​mple.com/' (zero-width char), labels longer than 63 chars, consecutive dots ('example..com'), or non-NFC Unicode that str.encode('idna') refuses.

Common situations: Copy-paste of internationalized domains with stray invisible characters; LLM-generated URLs with typographic dashes (en/em dash) instead of ASCII hyphens; corrupted strings from upstream data sources.

Related errors


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