iflytek/astron-agent · error · OutboundPolicyError

Outbound URL must not include a fragment

Error message

Outbound URL must not include a fragment

What it means

URL fragments (#anchor) are forbidden in outbound tool URLs. Fragments are client-side only and not sent to the server per spec, but HTTP clients and intermediate parsers handle them inconsistently, so the guard rejects them outright to eliminate the ambiguity.

Solutions

  1. Remove the #fragment portion from the URL.
  2. If '#' is part of a data value, percent-encode it: urllib.parse.quote('#') → %23.
  3. Strip programmatically: url.split('#', 1)[0].
  4. Fix the copy/paste or template step that introduced the anchor.

Example fix

// before
url = "https://api.example.com/items#section-2"
// after
url = "https://api.example.com/items".split('#', 1)[0]
Defensive patterns

Strategy: validation

Validate before calling

def has_no_fragment(url: str) -> bool:
    return not urlsplit(url).fragment

Try / catch

try:
    client.get(url)
except OutboundPolicyError:
    url = url.split('#', 1)[0]
    client.get(url)

Prevention

When it happens

Trigger: _parse_http_url receives a URL with a '#...' suffix — e.g. a link copied from a browser address bar including an anchor, or a template where an unescaped '#' appears in a path/query value.

Common situations: Copying https://site/docs#section into tool config; unencoded '#' inside a query value; SPA hash routes.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


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

Appendix: source

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

    return parsed


def _validate_url_characters(url: str) -> None:
    if any(ord(character) < 0x20 or ord(character) == 0x7F for character in url):
        raise OutboundPolicyError("Outbound URL contains control characters")


def _validate_parsed_http_url(parsed: SplitResult, port: Union[int, None]) -> None:
    if parsed.scheme.lower() not in _ALLOWED_SCHEMES:
        raise OutboundPolicyError("Only HTTP and HTTPS tool URLs are allowed")
    if not parsed.hostname:
        raise OutboundPolicyError("Outbound URL must include a hostname")
    if parsed.username is not None or parsed.password is not None:
        raise OutboundPolicyError("Outbound URL must not include user information")
    if "\\" in parsed.netloc:
        raise OutboundPolicyError("Outbound URL authority is invalid")
    if parsed.fragment:
        raise OutboundPolicyError("Outbound URL must not include a fragment")
    if port is not None and not 1 <= port <= 65535:
        raise OutboundPolicyError("Outbound URL port is invalid")


def _parse_networks(raw_value: str, setting_name: str) -> Tuple[IpNetwork, ...]:
    networks = []
    for entry in raw_value.split(","):
        value = entry.strip()
        if not value:
            continue
        try:
            networks.append(ipaddress.ip_network(value, strict=False))
        except ValueError as exc:
            raise OutboundPolicyError(f"Invalid {setting_name} entry") from exc
    return tuple(networks)


def _parse_domains(raw_value: str) -> Tuple[str, ...]:

View on GitHub (pinned to 5e758547a8)