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
- Remove `user:password@` from the URL and send credentials via HTTP headers (e.g. `Authorization: Bearer ...`) or query parameter as your API supports.
- If a token currently sits in the authority, move it into the path or headers and update the stored config/env value.
- When migrating from curl's `-u`, compute the Basic auth header (`base64(user:pass)`) instead of embedding it in the URL.
- 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
- Never embed credentials in URLs; use headers (Authorization/Bearer) or the client library's auth parameter.
- When translating curl -u usage, compute the Basic header rather than inlining user:pass@.
- Scrub netloc userinfo in a central URL-normalization helper before validation.
- Keep secrets out of URL-valued config fields so they never leak via logs.
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
- Remote resource URL must not include user information
- Outbound URL is malformed
- Outbound URL origin is invalid
- Skill resource URL is not allowed
- MODEL_URL_CHECK_FAILED
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)