{"record":{"id":"ae9179f8972c98aa","repo":"iflytek/astron-agent","slug":"outbound-url-must-include-a-hostname","errorCode":null,"errorMessage":"Outbound URL must include a hostname","messagePattern":"Outbound URL must include a hostname","errorType":"exception","errorClass":"OutboundPolicyError","httpStatus":null,"severity":"error","filePath":"core/plugin/link/infra/tool_exector/ssrf_guard.py","lineNumber":204,"sourceCode":"    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:\n    if any(ord(character) < 0x20 or ord(character) == 0x7F for character in url):\n        raise OutboundPolicyError(\"Outbound URL contains control characters\")\n\n\ndef _validate_parsed_http_url(parsed: SplitResult, port: Union[int, None]) -> None:\n    if parsed.scheme.lower() not in _ALLOWED_SCHEMES:\n        raise OutboundPolicyError(\"Only HTTP and HTTPS tool URLs are allowed\")\n    if not parsed.hostname:\n        raise OutboundPolicyError(\"Outbound URL must include a hostname\")\n    if parsed.username is not None or parsed.password is not None:\n        raise OutboundPolicyError(\"Outbound URL must not include user information\")\n    if \"\\\\\" in parsed.netloc:\n        raise OutboundPolicyError(\"Outbound URL authority is invalid\")\n    if parsed.fragment:\n        raise OutboundPolicyError(\"Outbound URL must not include a fragment\")\n    if port is not None and not 1 <= port <= 65535:\n        raise OutboundPolicyError(\"Outbound URL port is invalid\")\n\n\ndef _parse_networks(raw_value: str, setting_name: str) -> Tuple[IpNetwork, ...]:\n    networks = []\n    for entry in raw_value.split(\",\"):\n        value = entry.strip()\n        if not value:\n            continue\n        try:\n            networks.append(ipaddress.ip_network(value, strict=False))","sourceCodeStart":186,"sourceCodeEnd":222,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/core/plugin/link/infra/tool_exector/ssrf_guard.py#L186-L222","documentation":"_validate_parsed_http_url (part of the plugin SSRF guard) raises OutboundPolicyError when the parsed outbound URL has no hostname. The link tool's HTTP executor refuses scheme-only/malformed URLs before any network call is made.","triggerScenarios":"_parse_http_url gets a URL where urlsplit().hostname is None or empty — e.g. \"http:///api\", \"https://\", or a truncated string produced by slicing or template substitution with an empty host variable.","commonSituations":"Environment variable or tool config holding a truncated URL; f\"https://{host}\" with empty host; base-URL joining that dropped the domain.","solutions":["Ensure the URL includes a host, e.g. https://api.example.com/path.","Log the URL before the call and find where the host portion was lost (empty env var, wrong placeholder).","Fix the upstream configuration or URL-building code so the host is always present.","Validate host non-empty before invoking the plugin (see validation code)."],"exampleFix":"// before\nurl = f\"https://{host}/v1/tools\"  # host == \"\"\n// after\nif not host:\n    raise ValueError(\"tool host is required\")\nurl = f\"https://{host}/v1/tools\"","handlingStrategy":"validation","validationCode":"def has_hostname(url: str) -> bool:\n    return bool(urlsplit(url).hostname)","typeGuard":null,"tryCatchPattern":"try:\n    client.get(url)\nexcept OutboundPolicyError as e:\n    raise ConfigError(f\"tool endpoint needs a host: {url!r}\") from e","preventionTips":["Check base-URL env vars are non-empty at startup","Avoid f-string URL building with possibly-empty host variables","Validate configured endpoints once during service boot"],"tags":["security","ssrf","url-validation"],"backgroundTag":"invalid-url-format","analyzedSha":"5e758547a83371a5a4b29dadf4ac03e8dd527635","analyzedAt":"2026-09-12T08:03:51.356Z","contentChangedAt":"2026-09-12T08:03:51.356Z","schemaVersion":2},"datasetVersion":"2026-09-19T12:17:13.211Z"}