{"record":{"id":"ba743bc977cdd367","repo":"BerriAI/litellm","slug":"compresr-guardrail-api-base-has-no-host","errorCode":null,"errorMessage":"Compresr guardrail api_base has no host","messagePattern":"Compresr guardrail api_base has no host","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/proxy/guardrails/guardrail_hooks/compresr/compresr.py","lineNumber":137,"sourceCode":"    return addr\n\n\ndef _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","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/guardrails/guardrail_hooks/compresr/compresr.py#L119-L155","documentation":"ValueError from _validate_api_base when the configured Compresr api_base parses to an empty host — urlparse(url).hostname is None (or empty) even though the URL string is non-empty. Typical shapes are 'http://:8080', 'http:///path', or URLs where everything landed in scheme/path. The guardrail refuses to initialize against a hostless target; this is part of the SSRF defense-in-depth checks.","triggerScenarios":"api_base values like 'http://' (nothing else), 'http:///v1', 'http://:9000', or malformed strings with stray characters that push the host into other components; also scheme-only values after a bad string substitution in templated configs.","commonSituations":"Env var COMPRESR_API_BASE accidentally empty-but-prefixed (e.g., 'http://' + empty host); templating producing 'http:///'; copy-paste losing the hostname between slashes.","solutions":["Set a complete api_base with an explicit host: http://host:port or https://api.compresr.io.","Sanity-check: urlparse(api_base).hostname must be non-empty before deploying.","Fix empty COMPRESR_API_BASE env values and restart the proxy."],"exampleFix":"# before\nlitellm_params:\n  guardrail: compresr\n  api_base: http://:8080\n\n# after\nlitellm_params:\n  guardrail: compresr\n  api_base: http://compresr.internal:8080","handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\n\ndef has_host(url: str) -> bool:\n    return bool(urlparse(url).hostname)\n\nassert has_host(cfg_guardrail.get(\"api_base\", \"\")), \"api_base must include a host, e.g. http://compresr.internal:8080\"","typeGuard":"from urllib.parse import urlparse\nfrom typing import TypeGuard\ndef is_url_with_host(value: object) -> TypeGuard[str]:\n    return isinstance(value, str) and bool(urlparse(value).hostname)","tryCatchPattern":null,"preventionTips":["Template configs with non-empty host variables (guard against '' producing 'http://').","Lint all URL-typed config values with urlparse in CI.","Prefer full example URLs in config templates so deletions of the host are obvious in review."],"tags":["litellm","guardrails","compresr","url-validation","configuration","ssrf"],"backgroundTag":"config-validation-failed","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}