iflytek/astron-agent · error · OutboundPolicyError

Outbound URL origin is invalid

Error message

Outbound URL origin is invalid

What it means

`_origin` rejects URLs whose scheme is not exactly `http` or `https`, or whose hostname normalizes to empty, raising `OutboundPolicyError('Outbound URL origin is invalid')`. The library only treats http/https origins as safe for outbound tool calls; anything else (ftp://, file://, ws://) or a URL without a usable host is refused before any network I/O.

Solutions

  1. Prefix the scheme explicitly: ensure the URL starts with `http://` or `https://` before passing it to the guard.
  2. Check that a hostname is present after `//` in the URL; a trailing path with no authority (`https:///path`) is rejected.
  3. If a non-HTTP protocol is genuinely required, this guard intentionally forbids it — route through an approved HTTP gateway instead of weakening the check.
  4. Validate with `urlsplit(url)` in your own code: assert `url.scheme in ('http','https')` and `url.hostname` is non-empty before invoking the tool.

Example fix

// before
tool_url = "api.example.com/chat"   # no scheme -> origin invalid
// after
tool_url = "https://api.example.com/chat"
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlsplit

def has_http_origin(url) -> bool:
    if not isinstance(url, str):
        return False
    p = urlsplit(url)
    return p.scheme.lower() in ("http", "https") and bool(p.hostname)

Type guard

def is_http_url(value) -> bool:
    if not isinstance(value, str):
        return False
    p = urlsplit(value)
    return p.scheme in ("http", "https") and bool(p.hostname)

Try / catch

try:
    ensure_same_origin(base_url, candidate_url)
except OutboundPolicyError:
    raise ValueError(f"Tool URL must be an absolute http(s) URL with a hostname: {candidate_url!r}")

Prevention

When it happens

Trigger: `ensure_same_origin(base, candidate)` or `_endpoint(parsed)` receiving a URL like `ftp://host/path`, a scheme-less string (`example.com/api` with no `https://`), or `https:///path` (empty host). Also fires when `_normalize_hostname` returns an empty string because `parsed.hostname` was empty/whitespace.

Common situations: Config values written without the scheme (`TOOL_URL=api.example.com`); scheme accidentally stripped by a proxy/normalization step; relative URLs passed where absolute ones are required; `file://` or custom-scheme URLs smuggled into a tool definition.

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/363ba1c526f71aac. Report an issue: GitHub.

Appendix: source

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

            address,
            allow_private_endpoint=allow_private_endpoint,
            allow_literal_exception=literal_host,
        )
        return socket.socket(family=family, type=type_, proto=proto)

    return socket_factory


def _origin(url: str) -> Tuple[str, str, int]:
    try:
        parsed = urlsplit(url)
        scheme = parsed.scheme.lower()
        hostname = _normalize_hostname(parsed.hostname or "")
        port = parsed.port
    except (TypeError, ValueError) as exc:
        raise OutboundPolicyError("Outbound URL is malformed") from exc
    if scheme not in _ALLOWED_SCHEMES or not hostname:
        raise OutboundPolicyError("Outbound URL origin is invalid")
    if parsed.username is not None or parsed.password is not None:
        raise OutboundPolicyError("Outbound URL must not include user information")
    normalized_port = port if port is not None else (443 if scheme == "https" else 80)
    return scheme, hostname, normalized_port


def _parse_http_url(url: str) -> SplitResult:
    if not isinstance(url, str):
        raise OutboundPolicyError("Outbound URL is malformed")
    _validate_url_characters(url)
    try:
        parsed = urlsplit(url)
        port = parsed.port
    except (TypeError, ValueError) as exc:
        raise OutboundPolicyError("Outbound URL is malformed") from exc
    _validate_parsed_http_url(parsed, port)
    return parsed

View on GitHub (pinned to 5e758547a8)