{"record":{"id":"d1b43bdf8f2132e3","repo":"iflytek/astron-agent","slug":"remote-resource-url-authority-is-invalid","errorCode":null,"errorMessage":"Remote resource URL authority is invalid","messagePattern":"Remote resource URL authority is invalid","errorType":"exception","errorClass":"RemoteResourcePolicyError","httpStatus":null,"severity":"error","filePath":"core/plugin/aitools/common/clients/safe_download.py","lineNumber":219,"sourceCode":"        raise RemoteResourcePolicyError(\"Remote resource URL is malformed\")\n\n\ndef _validate_parsed_resource_url(\n    parsed: SplitResult,\n    port: Optional[int],\n) -> None:\n    if parsed.scheme.lower() not in _ALLOWED_SCHEMES:\n        raise RemoteResourcePolicyError(\n            \"Only HTTP and HTTPS remote resources are allowed\"\n        )\n    if not parsed.hostname:\n        raise RemoteResourcePolicyError(\"Remote resource URL must include a hostname\")\n    if parsed.username is not None or parsed.password is not None:\n        raise RemoteResourcePolicyError(\n            \"Remote resource URL must not include user information\"\n        )\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\")","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/core/plugin/aitools/common/clients/safe_download.py#L201-L237","documentation":"RemoteResourcePolicyError raised by _validate_parsed_resource_url when the URL's netloc (authority) contains a backslash character. The SSRF-safe download module in safe_download.py rejects backslashes in the authority because different HTTP parsers handle '\\\\' inconsistently, which can let an attacker smuggle a hostname past validation (e.g. 'https://example.com\\\\@evil.com/'). The check runs after scheme, hostname, and user-info checks during URL parsing.","triggerScenarios":"Passing a URL string to fetch_public_resource() (or any caller of _parse_resource_url) whose authority segment contains a literal backslash, e.g. 'https://bad\\\\host.example.com/file' or 'https://trusted.com\\\\@attacker.com/x'.","commonSituations":"User-supplied file URLs built by string concatenation where a Windows-style path fragment (C:\\\\...) leaks into the host portion; LLM/tool output containing escaped characters; template interpolation that injects backslashes into the URL before download.","solutions":["Remove or percent-encode backslashes from the URL before passing it to fetch_public_resource; build URLs with yarl.URL instead of string concatenation","Validate/sanitize the caller-supplied URL on the input side (reject or encode '\\\\' before it reaches the download path)","If the backslash is part of a path (not the authority), ensure it is properly placed after the host and percent-encoded as %5C"],"exampleFix":"// before\nurl = f\"https://{windows_path}\"  # e.g. https://cdn\\\\host/file\nawait fetch_public_resource(url)\n// after\nfrom yarl import URL\nsafe = str(URL(f\"https://cdn.example.com/{windows_path.replace('\\\\', '/')}\").with_scheme('https'))\nawait fetch_public_resource(safe)","handlingStrategy":"validation","validationCode":"def is_safe_url_authority(url: str) -> bool:\n    from urllib.parse import urlsplit\n    try:\n        netloc = urlsplit(url).netloc\n    except ValueError:\n        return False\n    return bool(netloc) and \"\\\\\" not in netloc and url.isprintable()","typeGuard":"def has_clean_authority(url: str) -> bool:\n    from urllib.parse import urlsplit\n    return \"\\\\\" not in urlsplit(url).netloc","tryCatchPattern":"from plugin.aitools.common.clients.safe_download import RemoteResourcePolicyError\ntry:\n    data = await fetch_public_resource(url)\nexcept RemoteResourcePolicyError as e:\n    log.warning(\"Rejected URL authority: %s\", e)","preventionTips":["Build URLs with yarl.URL or urllib.parse instead of string concatenation","Normalize Windows-style separators to '/' before placing values into URLs","Sanitize LLM/user output before using it as a URL component"],"tags":["url-validation","ssrf","security","python"],"backgroundTag":"invalid-url-format","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"}