{"record":{"id":"5278693aa1f13f9f","repo":"iflytek/astron-agent","slug":"httpclienterror-exc","errorCode":"HTTPClientError","errorMessage":"{exc}","messagePattern":"\\{exc\\}","errorType":"error_code","errorClass":"HTTPClientException","httpStatus":null,"severity":"error","filePath":"core/plugin/aitools/common/clients/safe_download.py","lineNumber":109,"sourceCode":"        parsed, _ = _validate_resource_url(url)\n        hostname = _normalize_hostname(parsed.hostname or \"\")\n        connector = aiohttp.TCPConnector(\n            use_dns_cache=False,\n            socket_factory=create_public_socket_factory(url),\n        )\n        timeout = aiohttp.ClientTimeout(\n            total=_positive_float_setting(AIOHTTP_CLIENT_TOTAL_TIMEOUT_KEY, 300.0),\n            connect=_positive_float_setting(AIOHTTP_CLIENT_CONNECT_TIMEOUT_KEY, 10.0),\n            sock_read=_positive_float_setting(AIOHTTP_CLIENT_READ_TIMEOUT_KEY, 60.0),\n        )\n        return await _download_resource(url, connector, timeout, max_bytes)\n    except RemoteResourcePolicyError as exc:\n        log.warning(\n            \"Remote resource download rejected, host={}, reason={}\", hostname, exc\n        )\n        if span is not None:\n            span.add_error_event(\"Remote resource download rejected\")\n        raise HTTPClientException.from_error_code(\n            CodeEnums.HTTPClientError,\n            extra_message=str(exc),\n        ) from exc\n    except Exception as exc:\n        log.debug(\n            \"Remote resource download failed, host={}, error_type={}\",\n            hostname,\n            type(exc).__name__,\n        )\n        if span is not None:\n            span.add_error_event(\"Remote resource download failed\")\n        raise HTTPClientException.from_error_code(\n            CodeEnums.HTTPClientError,\n            extra_message=\"Remote resource download failed\",\n        ) from exc\n\n\nasync def _download_resource(","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/core/plugin/aitools/common/clients/safe_download.py#L91-L127","documentation":"fetch_public_resource is the SSRF-hardened remote-file downloader. When any of its internal policy checks (RemoteResourcePolicyError) reject the URL, address, redirect, or payload, the policy reason is surfaced as an HTTPClientException with code HTTPClientError and the reason appended via extra_message. This means the download was refused by the library's safety validation, not that the network failed.","triggerScenarios":"Any RemoteResourcePolicyError raised inside fetch_public_resource: malformed URL, disallowed scheme, private/reserved target IP, user-info in URL, non-2xx status, oversized body, or invalid size limit (max_bytes <= 0). Callers include gen_params and req_ase_ability_ocr_service.","commonSituations":"Passing a file:// or ftp:// URL; pointing at localhost or an internal 10.x/192.168.x address; a URL containing user:pass@; a signed URL with a #fragment; a storage host that redirects (302) to a CDN; a file larger than the 50MB default limit.","solutions":["Read extra_message (str(exc) from the wrapped RemoteResourcePolicyError) — it states the exact policy reason; fix the URL accordingly.","Ensure the URL is a well-formed http(s) URL with hostname, no credentials, no fragment, and points at a public address.","If the resource legitimately exceeds the limit, pass a larger max_bytes explicitly.","For private object storage, use a URL under the configured OSS_DOWNLOAD_HOST origin/bucket so _is_configured_storage_url authorizes it (requires OSS_TYPE=s3)."],"exampleFix":"// before\nawait fetch_public_resource(\"ftp://files.example.com/report.pdf\")\n// after\nawait fetch_public_resource(\"https://files.example.com/report.pdf\")","handlingStrategy":"try-catch","validationCode":"from urllib.parse import urlsplit\np = urlsplit(url)\nassert url.startswith((\"http://\", \"https://\")) and p.hostname and \"@\" not in p.netloc and not p.fragment","typeGuard":"def is_safe_url(u): \n    from urllib.parse import urlsplit\n    try:\n        p = urlsplit(u)\n    except ValueError:\n        return False\n    return isinstance(u, str) and p.scheme in (\"http\", \"https\") and bool(p.hostname) and \"@\" not in p.netloc","tryCatchPattern":"try:\n    data = await fetch_public_resource(url)\nexcept HTTPClientException as e:\n    log.warning(\"download rejected: %s\", e)\n    return None","preventionTips":["Validate URLs are https with a public hostname at your API boundary","Keep resources under the configured OSS origin to benefit from the private-storage exception","Watch max_bytes against expected artifact sizes"],"tags":["network","security","ssrf","url-validation","python"],"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"}