{"record":{"id":"5ae8510183d5bff5","repo":"iflytek/astron-agent","slug":"outbound-address-is-blocked","errorCode":null,"errorMessage":"Outbound address is blocked","messagePattern":"Outbound address is blocked","errorType":"exception","errorClass":"OutboundPolicyError","httpStatus":null,"severity":"error","filePath":"core/plugin/link/infra/tool_exector/ssrf_guard.py","lineNumber":114,"sourceCode":"        \"\"\"Return whether deployment configuration authorizes this exact private endpoint.\"\"\"\n        # Private exceptions intentionally support only exact plain paths. Keeping\n        # semicolons in ``SplitResult.path`` prevents matrix parameters (including\n        # a trailing empty ``;``) from comparing equal to the configured path.\n        return (\n            \";\" not in parsed.path\n            and _endpoint(parsed) in self.allowed_private_endpoints\n        )\n\n    def validate_address(\n        self,\n        address: IpAddress,\n        *,\n        allow_private_endpoint: bool,\n        allow_literal_exception: bool,\n    ) -> None:\n        \"\"\"Validate the exact IP address that aiohttp is about to connect to.\"\"\"\n        if _matches_any(address, self.blocked_networks):\n            raise OutboundPolicyError(\"Outbound address is blocked\")\n        if allow_literal_exception and _matches_any(\n            address, self.allowed_literal_networks\n        ):\n            return\n        if _is_never_connect_address(address):\n            raise OutboundPolicyError(\"Outbound address is unsafe\")\n        if allow_private_endpoint:\n            return\n        if not _canonical_address(address).is_global:\n            raise OutboundPolicyError(\"Outbound address is not globally routable\")\n\n    def is_domain_blocked(self, hostname: str) -> bool:\n        \"\"\"Match configured domains on label boundaries, including subdomains.\"\"\"\n        for rule in self.blocked_domains:\n            if hostname == rule or hostname.endswith(\".\" + rule):\n                return True\n        return False\n","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/core/plugin/link/infra/tool_exector/ssrf_guard.py#L96-L132","documentation":"OutboundPolicyError raised in OutboundPolicy.validate_address when the IP address about to be connected to matches one of the configured blocked_networks (loaded from the SEGMENT/IP black-list env vars). This is the SSRF guard's explicit deny-list check, including IPv4-mapped IPv6 candidates. It means the destination IP is forbidden by deployment policy, not that the network is unreachable.","triggerScenarios":"Calling validate_url (directly or via create_socket_factory) with a URL whose host is a literal IP inside a blocked CIDR, or whose hostname DNS-resolves (at socket_factory time) to an address in the blocklist; also triggered for IPv4-mapped IPv6 addresses whose embedded IPv4 falls in a blocked network.","commonSituations":"Env misconfig: an IP black-list range (e.g. 10.0.0.0/8) is broader than intended and now covers a legitimate tool endpoint; a tool's endpoint moved into a blacklisted range; DNS resolves a public name to an internal/blocked address (split-horizon DNS); tests that connect to localhost while 127.0.0.1/8 was blacklisted.","solutions":["Inspect SEGMENT_BLACK_LIST_KEY / IP_BLACK_LIST_KEY env values and check whether the target IP falls inside one of those CIDRs (python -c \"import ipaddress;...\" to test membership).","If the endpoint is legitimately needed but private, add it to PRIVATE_ENDPOINT_ALLOW_LIST (exact scheme, host, port, plain path) so allow_private_endpoint bypasses, or add its exact IP to IP_WHITE_LIST_KEY when connecting by literal IP (allow_literal_exception short-circuits before unsafe checks).","Narrow or remove the over-broad black-list entry that accidentally covers the destination.","If the hostname unexpectedly resolves into a blocked range, fix DNS or point the tool at the correct public endpoint."],"exampleFix":"# before\nIP_BLACK_LIST=10.0.0.0/8   # tool endpoint 10.1.2.3 is now blocked\n# after\nIP_BLACK_LIST=10.0.0.0/8\nPRIVATE_ENDPOINT_ALLOW_LIST=http://10.1.2.3:8080/api","handlingStrategy":"validation","validationCode":"import ipaddress\n\ndef ip_is_blocked(host: str, blocked_cidrs: list[str]) -> bool:\n    try:\n        addr = ipaddress.ip_address(host)\n    except ValueError:\n        return False  # hostname; resolution-time check applies\n    for cidr in blocked_cidrs:\n        if addr in ipaddress.ip_network(cidr, strict=False):\n            return True\n    return False\n\n# call before issuing the request: ip_is_blocked(\"10.1.2.3\", os.getenv(\"IP_BLACK_LIST\").split(\",\"))","typeGuard":"def is_ip_literal(host: str) -> bool:\n    try:\n        ipaddress.ip_address(host)\n        return True\n    except ValueError:\n        return False","tryCatchPattern":"from plugin.link.infra.tool_exector.ssrf_guard import OutboundPolicyError\n\ntry:\n    policy.validate_url(url)\nexcept OutboundPolicyError as exc:\n    logger.warning(\"outbound blocked by policy: %s (url=%s)\", exc, url)\n    return None","preventionTips":["Keep the IP/segment black-list as narrow as the actual threat model requires and review it when tool endpoints change.","Use PRIVATE_ENDPOINT_ALLOW_LIST for sanctioned internal endpoints instead of weakening the black-list.","Log the resolved IP on every OutboundPolicyError to spot accidental blocklist coverage.","Add unit tests for each configured tool endpoint against the current policy before deploying config changes."],"tags":["ssrf","network","security","configuration"],"backgroundTag":"permission-denied","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"}