{"record":{"id":"12926b6fcdf0c285","repo":"sgl-project/sglang","slug":"allowed-media-domains-must-be-strings","errorCode":null,"errorMessage":"allowed media domains must be strings","messagePattern":"allowed media domains must be strings","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/utils/common.py","lineNumber":1522,"sourceCode":"    torch.manual_seed(seed)\n    if torch.cuda.is_available():\n        torch.cuda.manual_seed_all(seed)\n    if torch.xpu.is_available():\n        torch.xpu.manual_seed_all(seed)\n\n\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\"","sourceCodeStart":1504,"sourceCodeEnd":1540,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/utils/common.py#L1504-L1540","documentation":"The media-URL allowlist normalizer (_normalize_media_domain) requires every entry in --allowed-media-domains to be a Python str. Passing a non-string (e.g. bytes, None, or a list element of another type) is rejected early because domain matching must be exact string comparison after normalization.","triggerScenarios":"configure_media_url_security(allowed_media_domains=[b'example.com', ...]) or passing a config value deserialized as bytes/None; also _assert_media_url_allowed hitting a parsed.hostname that is not a str (rare, malformed URL parse).","commonSituations":"Loading server config from JSON/YAML where domains come back as bytes or null; programmatic construction of the allowlist from untyped data.","solutions":["Coerce all entries to str before passing: [str(d) for d in domains if d]","Filter out None entries when building the allowlist from optional config","Validate the config schema at load time"],"exampleFix":"# before\nconfigure_media_url_security(allowed_media_domains=[b'cdn.example.com'])\n# after\nconfigure_media_url_security(allowed_media_domains=['cdn.example.com'])","handlingStrategy":"type-guard","validationCode":"domains = [d for d in raw_domains if isinstance(d, str)]\nif len(domains) != len(raw_domains):\n    raise ConfigError('allowed_media_domains must all be strings')","typeGuard":"def is_valid_domain_list(v) -> bool:\n    return isinstance(v, (list, tuple)) and all(isinstance(d, str) and d.strip() for d in v)","tryCatchPattern":null,"preventionTips":["Type-check config at load time (pydantic/jsonschema)","Never construct the allowlist from untyped user input"],"tags":["validation","media","security","ssrf"],"backgroundTag":"config-validation-failed","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}