{"record":{"id":"3f21979b71199952","repo":"Panniantong/Agent-Reach","slug":"ssrf-blocked-internal-host-is-not-allowed","errorCode":null,"errorMessage":"SSRF blocked: internal host is not allowed","messagePattern":"SSRF blocked: internal host is not allowed","errorType":"exception","errorClass":"TranscribeError","httpStatus":null,"severity":"error","filePath":"agent_reach/transcribe.py","lineNumber":245,"sourceCode":"        if parsed.scheme not in {\"http\", \"https\"}:\n            raise TranscribeError(\"SSRF blocked: only public http(s) URLs are allowed\")\n\n    raw_authority = normalized_url.split(\"://\", 1)[1]\n    raw_authority = raw_authority.split(\"/\", 1)[0]\n    raw_authority = raw_authority.split(\"?\", 1)[0]\n    raw_authority = raw_authority.split(\"#\", 1)[0]\n    if \"\\\\\" in raw_authority or \"%\" in raw_authority:\n        raise TranscribeError(\"SSRF blocked: encoded or ambiguous URL host\")\n\n    raw_host = (parsed.hostname or \"\").strip().rstrip(\".\")\n    if not raw_host:\n        raise TranscribeError(\"SSRF blocked: URL host is missing\")\n    try:\n        host = raw_host.encode(\"idna\").decode(\"ascii\").lower().rstrip(\".\")\n    except UnicodeError:\n        raise TranscribeError(\"SSRF blocked: URL host is invalid\") from None\n    if host in _BLOCKED_HOSTS or host.endswith(\".localhost\"):\n        raise TranscribeError(\"SSRF blocked: internal host is not allowed\")\n    if _is_private_ip(host):\n        raise TranscribeError(\"SSRF blocked: private/internal IP is not allowed\")\n\n\ndef download_audio(url: str, out_dir: Path) -> Path:\n    \"\"\"Download audio with yt-dlp into out_dir; return the resulting file path.\"\"\"\n    _assert_safe_public_url(url)\n    _require(\"yt-dlp\")\n    template = out_dir / \"source.%(ext)s\"\n    _run(\n        [\n            \"yt-dlp\",\n            \"-x\",\n            \"--audio-format\",\n            \"m4a\",\n            \"--audio-quality\",\n            \"0\",\n            \"--no-playlist\",","sourceCodeStart":227,"sourceCodeEnd":263,"githubUrl":"https://github.com/Panniantong/Agent-Reach/blob/93ae1d18c37b707dec053c7c4f9d91cd8ef8943d/agent_reach/transcribe.py#L227-L263","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Serve the file from a publicly reachable host or pass the local file path directly to transcribe()","If you control the media server, bind it to a real (non-localhost-resolving) hostname reachable from the machine","Never feed agent-supplied URLs to this API without your own allowlist — the blocklist is deliberate but minimal"],"exampleFix":"# before\ndownload_audio(\"http://localhost:8080/ep.m4a\", out_dir)  # SSRF blocked: internal host is not allowed\n\n# after: local file takes the direct path\ntext = transcribe(\"/srv/media/ep.m4a\")","handlingStrategy":"validation","validationCode":"_BLOCKED = {\"localhost\", \"metadata.google.internal\"}\n\ndef host_not_internal(host: str) -> bool:\n    h = host.strip().rstrip(\".\").lower()\n    return h not in _BLOCKED and not h.endswith(\".localhost\")","typeGuard":null,"tryCatchPattern":"from agent_reach.transcribe import TranscribeError\ntry:\n    transcribe(url)\nexcept TranscribeError as e:\n    if \"internal host\" in str(e):\n        serve_locally_or_reject(url)  # never 'fix' by bypassing the guard\n    raise","preventionTips":["Never point transcription at localhost or *.localhost — use local file paths instead","Treat metadata.google.internal as an attack indicator in agent-facing logs","Keep an allowlist of trusted media hosts in front of transcribe()"],"tags":["ssrf","security","localhost","metadata-endpoint"],"backgroundTag":null,"analyzedSha":"93ae1d18c37b707dec053c7c4f9d91cd8ef8943d","analyzedAt":"2026-08-14T22:54:06.735Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}