{"record":{"id":"d1bf78e0f81f9e33","repo":"iflytek/astron-agent","slug":"outbound-url-must-not-include-user-information","errorCode":null,"errorMessage":"Outbound URL must not include user information","messagePattern":"Outbound URL must not include user information","errorType":"exception","errorClass":"OutboundPolicyError","httpStatus":null,"severity":"error","filePath":"core/plugin/link/infra/tool_exector/ssrf_guard.py","lineNumber":177,"sourceCode":"            allow_literal_exception=literal_host,\n        )\n        return socket.socket(family=family, type=type_, proto=proto)\n\n    return socket_factory\n\n\ndef _origin(url: str) -> Tuple[str, str, int]:\n    try:\n        parsed = urlsplit(url)\n        scheme = parsed.scheme.lower()\n        hostname = _normalize_hostname(parsed.hostname or \"\")\n        port = parsed.port\n    except (TypeError, ValueError) as exc:\n        raise OutboundPolicyError(\"Outbound URL is malformed\") from exc\n    if scheme not in _ALLOWED_SCHEMES or not hostname:\n        raise OutboundPolicyError(\"Outbound URL origin is invalid\")\n    if parsed.username is not None or parsed.password is not None:\n        raise OutboundPolicyError(\"Outbound URL must not include user information\")\n    normalized_port = port if port is not None else (443 if scheme == \"https\" else 80)\n    return scheme, hostname, normalized_port\n\n\ndef _parse_http_url(url: str) -> SplitResult:\n    if not isinstance(url, str):\n        raise OutboundPolicyError(\"Outbound URL is malformed\")\n    _validate_url_characters(url)\n    try:\n        parsed = urlsplit(url)\n        port = parsed.port\n    except (TypeError, ValueError) as exc:\n        raise OutboundPolicyError(\"Outbound URL is malformed\") from exc\n    _validate_parsed_http_url(parsed, port)\n    return parsed\n\n\ndef _validate_url_characters(url: str) -> None:","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/core/plugin/link/infra/tool_exector/ssrf_guard.py#L159-L195","documentation":"`_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.","triggerScenarios":"`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.","commonSituations":"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.","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."],"exampleFix":"// before\nurl = \"https://admin:secret@api.example.com/v1/tools\"\n// after\nurl = \"https://api.example.com/v1/tools\"  # send Authorization header instead","handlingStrategy":"validation","validationCode":"from urllib.parse import urlsplit\n\ndef has_no_userinfo(url) -> bool:\n    p = urlsplit(url)\n    return p.username is None and p.password is None","typeGuard":"def is_credential_free_url(value: str) -> bool:\n    p = urlsplit(value) if isinstance(value, str) else None\n    return p is not None and p.username is None and p.password is None","tryCatchPattern":"try:\n    ensure_same_origin(base_url, candidate_url)\nexcept OutboundPolicyError as exc:\n    if \"user information\" in str(exc):\n        raise ValueError(\"Strip user:pass@ from the URL and use an Authorization header\") from exc\n    raise","preventionTips":["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."],"tags":["security","ssrf","url-parsing","credentials"],"backgroundTag":"invalid-url","analyzedSha":"5e758547a83371a5a4b29dadf4ac03e8dd527635","analyzedAt":"2026-09-12T08:03:51.356Z","contentChangedAt":"2026-09-12T08:03:51.356Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}