iflytek/astron-agent · error · OutboundPolicyError
Outbound address is unsafe
Error message
Outbound address is unsafe
What it means
OutboundPolicyError raised in validate_address when the target IP falls into one of the hardcoded _NEVER_CONNECT_NETWORKS (unspecified, loopback, link-local, multicast, reserved, documentation ranges like 192.0.2.0/24, NAT64 64:ff9b::/96, 6to4 2002::/16, etc.). Unlike the blocklist error, this deny set is built into the library and cannot be configured away. It fires after the literal-whitelist exception check, so even whitelisted literals cannot reach these ranges.
Solutions
- Replace loopback/unspecified/documentation literal IPs with the real routable address of the target service.
- For genuinely intended private endpoints (RFC1918, site-local), register the exact endpoint in PRIVATE_ENDPOINT_ALLOW_LIST — but note loopback, link-local, multicast and reserved ranges are still rejected unconditionally.
- If DNS resolves to a never-connect address (e.g. 169.254.x), fix the DNS/service-discovery record.
- If you truly need one of these ranges (e.g. tests), run against a non-never-connect private address instead; the check is unconditional and not configurable.
Example fix
// before url = "http://127.0.0.1:8080/api" // after url = "http://192.168.10.5:8080/api" # plus PRIVATE_ENDPOINT_ALLOW_LIST=http://192.168.10.5:8080/api
Defensive patterns
Strategy: validation
Validate before calling
import ipaddress
_NEVER = ["0.0.0.0/8", "127.0.0.0/8", "169.254.0.0/16", "224.0.0.0/4", "240.0.0.0/4",
"192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24", "2001:db8::/32", "2002::/16"]
def is_never_connect(host: str) -> bool:
try:
addr = ipaddress.ip_address(host)
except ValueError:
return False
return any(addr in ipaddress.ip_network(n) for n in _NEVER)
# before the call: if is_never_connect("127.0.0.1"): raise ValueError("use a real endpoint") Type guard
def is_safe_literal_host(host: str) -> bool:
try:
addr = ipaddress.ip_address(host)
except ValueError:
return True # hostname, resolved later
return not (addr.is_loopback or addr.is_unspecified or addr.is_link_local or addr.is_multicast or addr.is_reserved) Try / catch
try:
policy.validate_url(url)
except OutboundPolicyError as exc:
if "unsafe" in str(exc):
raise ValueError(f"{url} targets a reserved/loopback network; use a routable endpoint") from exc
raise Prevention
- Never hardcode localhost, 0.0.0.0, or RFC 5737 documentation IPs in tool configs.
- Remember the never-connect check is unconditional: even IP_WHITE_LIST or PRIVATE_ENDPOINT_ALLOW_LIST cannot bypass it.
- Watch for NAT64 (64:ff9b::/96) and 6to4 (2002::/16) synthesized addresses when dual-stack DNS is in play.
- Alert on this error in production: it often indicates a DNS or misconfiguration problem rather than an attack.
When it happens
Trigger: validate_url/create_socket_factory given a literal IP in a never-connect range (e.g. http://127.0.0.1, http://0.0.0.0, http://[::1], http://203.0.113.5), or a hostname whose DNS resolution in socket_factory returns such an address; also via 64:ff9b::/96 or 2002::/16 NAT/6to4 embedded addresses.
Common situations: Developers pointing tools at localhost or a docker-internal IP without adding it to PRIVATE_ENDPOINT_ALLOW_LIST (note: private-endpoint allow-listing does NOT bypass never-connect ranges for loopback — loopback is rejected unconditionally); using documentation/example IPs (203.0.113.x, 2001:db8::) copied from docs; DNS returning link-local (169.254.x) addresses during infrastructure issues.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- MODEL_URL_CHECK_FAILED
- Resolved remote resource address is invalid
- HTTPClientError
- Remote resource address is unsafe
- Outbound address is blocked
AI-assisted analysis of iflytek/astron-agent@5e758547a8 (2026-09-12).
Data as JSON: /api/errors/a132e6b7272b7267.
Report an issue: GitHub.
Appendix: source
Thrown at core/plugin/link/infra/tool_exector/ssrf_guard.py:120
and _endpoint(parsed) in self.allowed_private_endpoints
)
def validate_address(
self,
address: IpAddress,
*,
allow_private_endpoint: bool,
allow_literal_exception: bool,
) -> None:
"""Validate the exact IP address that aiohttp is about to connect to."""
if _matches_any(address, self.blocked_networks):
raise OutboundPolicyError("Outbound address is blocked")
if allow_literal_exception and _matches_any(
address, self.allowed_literal_networks
):
return
if _is_never_connect_address(address):
raise OutboundPolicyError("Outbound address is unsafe")
if allow_private_endpoint:
return
if not _canonical_address(address).is_global:
raise OutboundPolicyError("Outbound address is not globally routable")
def is_domain_blocked(self, hostname: str) -> bool:
"""Match configured domains on label boundaries, including subdomains."""
for rule in self.blocked_domains:
if hostname == rule or hostname.endswith("." + rule):
return True
return False
def ensure_same_origin(base_url: str, candidate_url: str) -> None:
"""Reject path or authentication data that changes scheme, host, or port."""
if _origin(base_url) != _origin(candidate_url):
raise OutboundPolicyError("Tool path must not change the endpoint origin")
View on GitHub (pinned to 5e758547a8)