{"record":{"id":"c4b1452fe683b3da","repo":"sgl-project/sglang","slug":"invalid-allowed-media-domain-domain-r-provide-a","errorCode":null,"errorMessage":"Invalid allowed media domain {domain!r}: provide a hostname only","messagePattern":"Invalid allowed media domain (.+?): provide a hostname only","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/utils/common.py","lineNumber":1528,"sourceCode":"\n_mm_http_session = threading.local()\n\n_DEFAULT_MEDIA_URL_MAX_FILE_SIZE_MB = 64\n_MAX_MEDIA_URL_REDIRECTS = 5\n_MEDIA_URL_REDIRECT_STATUS_CODES = {301, 302, 303, 307, 308}\n_allowed_media_domains: frozenset[str] = frozenset()\n_media_url_max_file_size_bytes = _DEFAULT_MEDIA_URL_MAX_FILE_SIZE_MB * 1024 * 1024\n\n\ndef _normalize_media_domain(domain: str) -> str:\n    if not isinstance(domain, str):\n        raise ValueError(\"allowed media domains must be strings\")\n\n    domain = domain.strip().rstrip(\".\")\n    if not domain:\n        raise ValueError(\"allowed media domains cannot be empty\")\n    if \"://\" in domain or any(char in domain for char in \"/?#@\"):\n        raise ValueError(\n            f\"Invalid allowed media domain {domain!r}: provide a hostname only\"\n        )\n\n    # Brackets are URL syntax, not part of an IPv6 hostname.\n    if domain.startswith(\"[\") and domain.endswith(\"]\"):\n        domain = domain[1:-1]\n    try:\n        return str(ipaddress.ip_address(domain))\n    except ValueError:\n        if \":\" in domain:\n            raise ValueError(\n                f\"Invalid allowed media domain {domain!r}: ports are not supported\"\n            )\n\n    try:\n        normalized = domain.encode(\"idna\").decode(\"ascii\").lower()\n    except UnicodeError as e:\n        raise ValueError(f\"Invalid allowed media domain {domain!r}\") from e","sourceCodeStart":1510,"sourceCodeEnd":1546,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/utils/common.py#L1510-L1546","documentation":"An allowed-media-domains entry contained URL syntax characters — a scheme separator '://', or any of / ? # @ — indicating a URL or userinfo form was supplied instead of a bare hostname. The allowlist matches hostnames only, so paths, schemes, or user@host forms are rejected to keep matching unambiguous.","triggerScenarios":"Passing 'https://example.com/path', 'user@example.com', or 'example.com/img.png' as an allowlist entry to configure_media_url_security.","commonSituations":"Users paste full URLs into --allowed-media-domains instead of just the domain; config templating injects scheme prefixes.","solutions":["Provide only the hostname: 'example.com' instead of 'https://example.com/...'","If subdomains matter, list each hostname explicitly (matching is exact)","Add a startup lint that rejects entries containing '://' or /?#@"],"exampleFix":"# before\nconfigure_media_url_security(allowed_media_domains=['https://cdn.example.com/img.png'])\n# after\nconfigure_media_url_security(allowed_media_domains=['cdn.example.com'])","handlingStrategy":"validation","validationCode":"BAD = set(':/?#@')\nfor d in domains:\n    if '://' in d or any(c in d for c in BAD):\n        raise ConfigError(f'{d!r}: hostname only, no scheme/path/userinfo')","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Document hostname-only format next to the CLI flag","Strip schemes in config preprocessing: d.split('://')[-1].split('/')[0]"],"tags":["validation","media","security","ssrf","url-parsing"],"backgroundTag":"url-validation-failed","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}