{"record":{"id":"ccf9e42ba0f4a474","repo":"iflytek/astron-agent","slug":"resolved-outbound-address-is-invalid","errorCode":null,"errorMessage":"Resolved outbound address is invalid","messagePattern":"Resolved outbound address is invalid","errorType":"exception","errorClass":"OutboundPolicyError","httpStatus":null,"severity":"error","filePath":"core/plugin/link/infra/tool_exector/ssrf_guard.py","lineNumber":155,"sourceCode":"        raise OutboundPolicyError(\"Tool path must not change the endpoint origin\")\n\n\ndef create_socket_factory(\n    policy: OutboundPolicy,\n    target_url: str,\n) -> Callable[[aiohttp.AddrInfoType], socket.socket]:\n    \"\"\"Create an aiohttp socket factory that checks the actual target sockaddr.\"\"\"\n    parsed = policy.validate_url(target_url)\n    hostname = _normalize_hostname(parsed.hostname or \"\")\n    literal_host = _parse_ip(hostname) is not None\n    allow_private_endpoint = policy.is_private_endpoint_allowed(parsed)\n\n    def socket_factory(addr_info: aiohttp.AddrInfoType) -> socket.socket:\n        family, type_, proto, _, sockaddr = addr_info\n        try:\n            address = ipaddress.ip_address(sockaddr[0])\n        except ValueError as exc:\n            raise OutboundPolicyError(\"Resolved outbound address is invalid\") from exc\n        policy.validate_address(\n            address,\n            allow_private_endpoint=allow_private_endpoint,\n            allow_literal_exception=literal_host,\n        )\n        return socket.socket(family=family, type=type_, proto=proto)\n\n    return socket_factory\n\n\ndef _origin(url: str) -> Tuple[str, str, int]:\n    try:\n        parsed = urlsplit(url)\n        scheme = parsed.scheme.lower()\n        hostname = _normalize_hostname(parsed.hostname or \"\")\n        port = parsed.port\n    except (TypeError, ValueError) as exc:\n        raise OutboundPolicyError(\"Outbound URL is malformed\") from exc","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/core/plugin/link/infra/tool_exector/ssrf_guard.py#L137-L173","documentation":"OutboundPolicyError raised inside socket_factory when ipaddress.ip_address() cannot parse the sockaddr returned by DNS resolution for the aiohttp connection. This indicates the resolver produced an address string that is not a valid IP literal — essentially never expected from a healthy stack, so it is treated as a hard policy failure rather than retried. The chained ValueError from ip_address is preserved as __cause__.","triggerScenarios":"create_socket_factory's returned socket_factory receiving an aiohttp.AddrInfoType whose sockaddr[0] is not a parseable IP string — e.g. a custom resolver or patched event loop returning hostnames/empty strings in addr_info, or an unusual AI_* family entry the address parser cannot handle.","commonSituations":"Test environments stubbing aiohttp's resolver with malformed sockaddr values; custom Resolver implementations returning sockaddr with a hostname instead of an IP; corrupted/crafted addrinfo from a custom c-ares build; IPv6 scope-id strings ('fe80::1%eth0') that ipaddress rejects in some contexts.","solutions":["Check any custom aiohttp Resolver in use — it must return IP literals (not hostnames) in sockaddr; fix it to resolve before returning addr_info.","Inspect the chained cause (OutboundPolicyError.__cause__) to see which address string failed parsing.","If tests stub addr_info, construct it with real ipaddress-parseable strings, e.g. ('93.184.216.34', 80) or ('2606:2800:220:1:248:1893:25c8:1946', 80, 0, 0).","Ensure the system resolver (getaddrinfo/c-ares) is healthy; test with socket.getaddrinfo(host, port) directly.","Strip IPv6 scope IDs (%eth0) at the resolver level or use a non-link-local address."],"exampleFix":"# before (custom test stub)\naddr_info = (socket.AF_INET, socket.SOCK_STREAM, 6, '', ('example.com', 80))\n# after\naddr_info = (socket.AF_INET, socket.SOCK_STREAM, 6, '', ('93.184.216.34', 80))","handlingStrategy":"try-catch","validationCode":"import socket, ipaddress\n\ndef resolver_returns_ips(host: str) -> bool:\n    try:\n        infos = socket.getaddrinfo(host, None)\n    except socket.gaierror:\n        return False\n    return all(_parseable(info[4][0]) for info in infos)\n\ndef _parseable(value: str) -> bool:\n    try:\n        ipaddress.ip_address(value)\n        return True\n    except ValueError:\n        return False","typeGuard":"def is_valid_addr_info(addr_info) -> bool:\n    try:\n        sockaddr = addr_info[4]\n        ipaddress.ip_address(sockaddr[0])\n        return True\n    except (IndexError, ValueError, TypeError):\n        return False","tryCatchPattern":"try:\n    sock = socket_factory(addr_info)\nexcept OutboundPolicyError as exc:\n    logger.error(\"bad resolved addr_info %r: cause=%r\", addr_info, exc.__cause__)\n    raise","preventionTips":["Do not stub aiohttp's resolver with hostname-bearing sockaddr values; always resolve to IP literals.","Validate custom Resolver output in unit tests with ipaddress.ip_address().","Inspect exc.__cause__ (the original ValueError) to identify the malformed address string.","Keep the system resolver and c-ares builds standard unless you control addr_info formatting."],"tags":["ssrf","network","dns","socket"],"backgroundTag":"invalid-argument-value","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"}