iflytek/astron-agent · error · OutboundPolicyError

Outbound hostname is invalid

Error message

Outbound hostname is invalid

What it means

OutboundPolicyError from _normalize_hostname when the given hostname fails yarl's URL.build() host validation (TypeError/ValueError/UnicodeError). It guarantees every hostname checked by the SSRF guard is a syntactically valid host before IP/allow-list evaluation.

Solutions

  1. Fix the source URL so its host is a valid DNS name or IP (no spaces, underscores, brackets, or stray port).
  2. If handling internationalized domains, pre-encode them to punycode (IDNA) before validation.
  3. Check upstream parsing code that extracts the hostname — it may be grabbing too much or too little of the URL.
  4. Catch OutboundPolicyError at the plugin boundary and surface a clear 'invalid target host' message to the caller.

Example fix

// before
url = "http://my_server space.internal/api"
await validate_url(url)  # underscore+space host
// after
url = "http://my-server-space.internal/api"
await validate_url(url)
Defensive patterns

Strategy: validation

Validate before calling

from yarl import URL
def is_valid_host(value) -> bool:
    if not isinstance(value, str) or not value.strip():
        return False
    try:
        return bool(URL.build(scheme="http", host=value.strip().lower().rstrip(".")).raw_host)
    except (TypeError, ValueError, UnicodeError):
        return False

Type guard

def is_str_hostname(v) -> bool:
    return isinstance(v, str) and bool(v.strip()) and " " not in v and ";" not in v

Try / catch

try:
    await validate_url(target)
except OutboundPolicyError as exc:
    logger.warning("Blocked invalid outbound URL %r: %s", target, exc)
    return ToolResult(error="invalid target host")

Prevention

When it happens

Trigger: validate_url, create_socket_factory, _origin, or _parse_domains pass a hostname that URL.build(scheme='http', host=value) rejects: illegal characters, IDN/Unicode encoding failures, or a value that isn't a string (TypeError).

Common situations: A tool config or plugin passes a URL whose host contains spaces/underscores/brackets, non-ASCII domains that fail IDNA, or code extracts a host incorrectly (e.g. includes port or path in the host variable).

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of iflytek/astron-agent@5e758547a8 (2026-09-12). Data as JSON: /api/errors/da007fbb4bd0cf0a. Report an issue: GitHub.

Appendix: source

Thrown at core/plugin/link/infra/tool_exector/ssrf_guard.py:282

                "PRIVATE_ENDPOINT_ALLOW_LIST entries must not include params or a query"
            )
        endpoints.append(_endpoint(parsed))
    return tuple(endpoints)


def _endpoint(parsed: SplitResult) -> Endpoint:
    scheme, hostname, port = _origin(parsed.geturl())
    return scheme, hostname, port, parsed.path or "/"


def _normalize_hostname(hostname: str) -> str:
    value = hostname.strip().lower().rstrip(".")
    if _parse_ip(value) is not None:
        return value
    try:
        normalized = URL.build(scheme="http", host=value).raw_host
    except (TypeError, ValueError, UnicodeError) as exc:
        raise OutboundPolicyError("Outbound hostname is invalid") from exc
    if not normalized:
        raise OutboundPolicyError("Outbound hostname is invalid")
    return normalized.rstrip(".")


def _parse_ip(value: str) -> Union[IpAddress, None]:
    try:
        return ipaddress.ip_address(value)
    except ValueError:
        return None


def _matches_any(address: IpAddress, networks: Tuple[IpNetwork, ...]) -> bool:
    candidates: Tuple[IpAddress, ...] = (address,)
    if isinstance(address, ipaddress.IPv6Address) and address.ipv4_mapped:
        candidates += (address.ipv4_mapped,)
    return any(
        candidate.version == network.version and candidate in network

View on GitHub (pinned to 5e758547a8)