{"record":{"id":"554d2f8931c24c85","repo":"BerriAI/litellm","slug":"compresr-guardrail-api-base-host-r-is-a-blocked","errorCode":null,"errorMessage":"Compresr guardrail api_base {host!r} is a blocked cloud-metadata host","messagePattern":"Compresr guardrail api_base (.+?) is a blocked cloud-metadata host","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/proxy/guardrails/guardrail_hooks/compresr/compresr.py","lineNumber":140,"sourceCode":"def _validate_api_base(url: str) -> str:\n    \"\"\"Return ``url`` if it passes basic outbound-target checks, else raise.\n\n    Best-effort defense in depth for a mis/maliciously-configured ``api_base``:\n    rejects non-http(s) schemes and cloud-metadata IPs/hosts (incl. alternate IP\n    encodings); private ranges are allowed for on-prem deployments. NOT a complete\n    SSRF control — no DNS resolution, and the shared client follows redirects and\n    re-resolves DNS (TOCTOU / rebinding); ``api_base`` is trusted operator config,\n    so this is an accepted limitation.\n    \"\"\"\n    parsed: Final = urlparse(url)\n    if parsed.scheme not in (\"http\", \"https\"):\n        raise ValueError(f\"Compresr guardrail api_base must be http or https, got scheme={parsed.scheme!r}\")\n    host: Final = (parsed.hostname or \"\").lower()\n    if not host:\n        raise ValueError(\"Compresr guardrail api_base has no host\")\n    ip_literal: Final = _parse_ip_literal(host)\n    if host in _BLOCKED_METADATA_HOSTS or (ip_literal is not None and ip_literal in _BLOCKED_METADATA_IPS):\n        raise ValueError(f\"Compresr guardrail api_base {host!r} is a blocked cloud-metadata host\")\n    return url\n\n\ndef _is_str_object_dict(value: object) -> TypeGuard[dict[str, object]]:  # guard-ok: isinstance narrows correctly; predicate is trivially correct  # fmt: skip\n    return isinstance(value, dict)\n\n\ndef _is_object_list(value: object) -> TypeGuard[list[object]]:  # guard-ok: isinstance narrows correctly; predicate is trivially correct  # fmt: skip\n    return isinstance(value, list)\n\n\ndef _replace_text_in_content(content: object, new_text: str) -> object:\n    \"\"\"Write ``new_text`` back into a ``content`` value, preserving shape.\n\n    ``str`` content is replaced directly. An all-text part list collapses to a\n    single part carrying the last declared cache_control breakpoint. Anything\n    else is returned unchanged: breakpoints are positional, so one compressed\n    string cannot be written back across a non-text part without moving text","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/guardrails/guardrail_hooks/compresr/compresr.py#L122-L158","documentation":"ValueError from _validate_api_base: the Compresr guardrail's api_base host (literal or resolved from alternate IP encodings) matches a cloud-metadata endpoint (e.g., 169.254.169.254 and friends in _BLOCKED_METADATA_HOSTS/_BLOCKED_METADATA_IPS). The guardrail intentionally refuses to send request content (which contains user prompts) to link-local metadata services — a guard against SSRF/exfiltration via malicious or mistaken config. The offending host is echoed in {host!r}.","triggerScenarios":"api_base set (deliberately or via compromise of config) to http://169.254.169.254/..., http://metadata.google.internal, alternate encodings like decimal/hex IP forms of 169.254.169.254, or [fd00:ec2::254] style metadata IPs — _parse_ip_literal normalizes encodings before matching.","commonSituations":"Security testing/red-team configs probing whether the proxy will leak prompts to the instance-metadata service; misconfigured internal service discovery returning the metadata IP; penetration-test findings where this block correctly fires.","solutions":["Point api_base at the real Compresr service host, never at link-local/metadata addresses.","Audit how the api_base value got into the config — if you didn't set it, treat it as a compromise indicator and investigate config provenance.","Re-run with a corrected COMPRESR_API_BASE / api_key setup and restart the proxy."],"exampleFix":"# before — blocked metadata target\nlitellm_params:\n  guardrail: compresr\n  api_base: http://169.254.169.254/latest/meta-data\n\n# after\nlitellm_params:\n  guardrail: compresr\n  api_base: https://compresr.mycompany.internal","handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\nimport ipaddress\nMETADATA = {\"169.254.169.254\", \"metadata.google.internal\"}\n\ndef rejects_metadata(url: str) -> bool:\n    host = (urlparse(url).hostname or \"\").lower()\n    try:\n        ip = ipaddress.ip_address(host)\n        return ip.is_link_local or str(ip) in METADATA or host in METADATA\n    except ValueError:\n        return host in METADATA\n\nassert not rejects_metadata(api_base), f\"api_base targets cloud metadata: {api_base}\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat config-provenance seriously: only trusted CI may modify guardrail api_base values.","Add config-lint rules that reject link-local (169.254.0.0/16, fe80::/10) and well-known metadata hosts.","If this error appears unexpectedly, investigate as a potential supply-chain/config compromise, not just a typo."],"tags":["litellm","guardrails","compresr","ssrf","cloud-metadata","security","configuration"],"backgroundTag":"ssrf-blocked-host","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}