{"record":{"id":"95753ea80a25f4ae","repo":"iflytek/astron-agent","slug":"remote-resource-hostname-is-invalid","errorCode":null,"errorMessage":"Remote resource hostname is invalid","messagePattern":"Remote resource hostname is invalid","errorType":"exception","errorClass":"RemoteResourcePolicyError","httpStatus":null,"severity":"error","filePath":"core/plugin/aitools/common/clients/safe_download.py","lineNumber":235,"sourceCode":"        )\n    if \"\\\\\" in parsed.netloc:\n        raise RemoteResourcePolicyError(\"Remote resource URL authority is invalid\")\n    if parsed.fragment:\n        raise RemoteResourcePolicyError(\n            \"Remote resource URL must not include a fragment\"\n        )\n    if port is not None and not 1 <= port <= 65535:\n        raise RemoteResourcePolicyError(\"Remote resource URL port is invalid\")\n\n\ndef _normalize_hostname(hostname: str) -> str:\n    value = hostname.strip().lower().rstrip(\".\")\n    if _parse_ip(value) is not None:\n        return value\n    try:\n        normalized = URL.build(scheme=\"http\", host=value).raw_host\n    except (TypeError, ValueError, UnicodeError) as exc:\n        raise RemoteResourcePolicyError(\"Remote resource hostname is invalid\") from exc\n    if not normalized:\n        raise RemoteResourcePolicyError(\"Remote resource hostname is invalid\")\n    return normalized.rstrip(\".\")\n\n\ndef _parse_ip(value: str) -> Optional[IpAddress]:\n    try:\n        return ipaddress.ip_address(value)\n    except ValueError:\n        return None\n\n\ndef _validate_destination_address(\n    address: IpAddress,\n    *,\n    allow_private_storage: bool,\n) -> None:\n    canonical = _canonical_address(address)","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/core/plugin/aitools/common/clients/safe_download.py#L217-L253","documentation":"RemoteResourcePolicyError raised by _normalize_hostname when yarl.URL.build() rejects the hostname string. yarl enforces RFC-compliant host syntax (it rejects characters like '_', empty strings after stripping, spaces, and other invalid characters that Python's urlsplit alone would accept). This normalizer is used by fetch_public_resource, _validate_resource_url, and _is_configured_storage_url.","triggerScenarios":"A URL whose hostname contains characters yarl refuses, such as underscores ('my_host.example.com'), spaces, or other non-RFC characters, passed to fetch_public_resource or validated against the configured S3 storage origin.","commonSituations":"Internal hostnames with underscores in dev environments (common legacy naming); URLs built from env vars containing whitespace; typos like double dots or trailing punctuation.","solutions":["Replace the hostname with an RFC-valid name (letters, digits, hyphens; underscores are not valid in hostnames)","If you control DNS/naming, rename the host or use the IP literal (which bypasses yarl normalization via the _parse_ip path)","Percent-encode is not allowed in hostnames — instead register a DNS alias (CNAME) without underscores"],"exampleFix":"// before\nurl = \"http://my_service.internal:8080/file\"  # underscore host\nawait fetch_public_resource(url)\n// after\nurl = \"http://my-service.internal:8080/file\"  # or http://10.0.0.5:8080/file\nawait fetch_public_resource(url)","handlingStrategy":"validation","validationCode":"import re\nHOST_RE = re.compile(r\"^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$\", re.I)\ndef is_valid_hostname(host: str) -> bool:\n    return bool(host) and \"_\" not in host and HOST_RE.fullmatch(host) is not None","typeGuard":null,"tryCatchPattern":"from plugin.aitools.common.clients.safe_download import RemoteResourcePolicyError\ntry:\n    data = await fetch_public_resource(url)\nexcept RemoteResourcePolicyError as e:\n    if \"hostname is invalid\" in str(e):\n        log.error(\"URL host fails RFC hostname rules: %s\", url)\n    raise","preventionTips":["Avoid underscores in hostnames (use hyphens); underscore hosts pass urlsplit but fail yarl","Trim whitespace from host values sourced from env/config","Test hostnames against yarl.URL.build(scheme='http', host=...) early in CI"],"tags":["url-validation","hostname","ssrf","python","yarl"],"backgroundTag":"invalid-url","analyzedSha":"5e758547a83371a5a4b29dadf4ac03e8dd527635","analyzedAt":"2026-09-12T08:03:51.356Z","contentChangedAt":"2026-09-12T08:03:51.356Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}