iflytek/astron-agent · error · OutboundPolicyError

Outbound URL must not include user information

Error message

Outbound URL must not include user information

What it means

`_origin` rejects any URL containing userinfo (`user:pass@host`) with `OutboundPolicyError('Outbound URL must not include user information')`. Embedding credentials in a URL authority is both a secret-leakage risk (URLs get logged) and an SSRF confusion vector, so the guard refuses outright rather than stripping them.

Solutions

  1. Remove `user:password@` from the URL and send credentials via HTTP headers (e.g. `Authorization: Bearer ...`) or query parameter as your API supports.
  2. If a token currently sits in the authority, move it into the path or headers and update the stored config/env value.
  3. When migrating from curl's `-u`, compute the Basic auth header (`base64(user:pass)`) instead of embedding it in the URL.
  4. Reject URLs containing `@` in the netloc at your config-loading boundary so this never reaches the runtime guard.

Example fix

// before
url = "https://admin:secret@api.example.com/v1/tools"
// after
url = "https://api.example.com/v1/tools"  # send Authorization header instead
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlsplit

def has_no_userinfo(url) -> bool:
    p = urlsplit(url)
    return p.username is None and p.password is None

Type guard

def is_credential_free_url(value: str) -> bool:
    p = urlsplit(value) if isinstance(value, str) else None
    return p is not None and p.username is None and p.password is None

Try / catch

try:
    ensure_same_origin(base_url, candidate_url)
except OutboundPolicyError as exc:
    if "user information" in str(exc):
        raise ValueError("Strip user:pass@ from the URL and use an Authorization header") from exc
    raise

Prevention

When it happens

Trigger: `ensure_same_origin` or `_endpoint` given a URL like `https://admin:secret@api.example.com/`, or even `https://@host/` (empty username still counts, since `parsed.username == ''` is not None). `_endpoint` re-parses `parsed.geturl()`, so any SplitResult carrying netloc userinfo triggers it.

Common situations: Copy-pasted curl commands with `-u user:pass` inlined into the URL; legacy service configs that stored API keys as `token@host`; generated webhook URLs that embed a token in the authority instead of the path or a header.

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

Appendix: source

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

            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


def _validate_url_characters(url: str) -> None:

View on GitHub (pinned to 5e758547a8)