{"record":{"id":"01e9a4cb274c6b1e","repo":"langflow-ai/langflow","slug":"webhook-url-has-an-invalid-host-exc","errorCode":null,"errorMessage":"webhook url has an invalid host: {exc}","messagePattern":"webhook url has an invalid host: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/backend/base/langflow/api/v1/a2a_utils.py","lineNumber":132,"sourceCode":"        # unicode urlparse hostname, so an IDN webhook is pinned/resolved by the exact ASCII host\n        # the connection uses (else the pin silently misses: TOCTOU rebind for IDN hosts). httpx.URL\n        # raises InvalidURL (not ValueError) for an IDNA-invalid host, so keep it inside the try.\n        host = webhook_pin_host(url)\n        # Hard floor: reject private/metadata IPs even when global SSRF protection is off\n        # (validate_and_resolve_url returns [] with NO enforcement in that case).\n        # resolve_hostname handles IP-literal hosts too; the blocking resolve runs off-loop.\n        floor_ips = await asyncio.to_thread(resolve_hostname, host)\n        blocked = [ip for ip in floor_ips if is_ip_blocked(ip)]\n        if blocked:\n            msg = f\"webhook url resolves to a blocked address: {', '.join(blocked)}\"\n            raise ValueError(msg)\n        # Then the framework check for allowlist / CGNAT / is_global extras + pinned IPs.\n        _url, validated_ips = await asyncio.to_thread(validate_and_resolve_url, url)\n    except httpx.InvalidURL as exc:\n        # Callers only guard ValueError; without translating this an IDNA-invalid host would 500 the\n        # caller (or escape dispatch) instead of failing closed as an unsafe webhook.\n        msg = f\"webhook url has an invalid host: {exc}\"\n        raise ValueError(msg) from exc\n    except SSRFProtectionError as exc:\n        msg = f\"webhook url is not allowed: {exc}\"\n        raise ValueError(msg) from exc\n    # Fall back to the floor IPs so dispatch can still DNS-pin with the global toggle off.\n    return validated_ips or floor_ips\n\n\nasync def folder_auth_type(flow: Flow, session: AsyncSession) -> str:\n    \"\"\"Read the flow's folder ``auth_type`` (``\"none\"`` | ``\"apikey\"`` | ``\"oauth\"``).\n\n    The single source of truth for what the card advertises (resolve_card_security)\n    and what the JSON-RPC route enforces, so the two can't drift. Plaintext read,\n    no decrypt. No folder / missing folder -> ``\"none\"`` (public).\n    \"\"\"\n    if flow.folder_id is None:\n        return \"none\"\n    # Query the folder explicitly; lazy-loading flow.folder would raise in async.\n    folder = (await session.exec(select(Folder).where(Folder.id == flow.folder_id))).first()","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/langflow-ai/langflow/blob/976ec789d2886a86de109c044d089d68e96c9a35/src/backend/base/langflow/api/v1/a2a_utils.py#L114-L150","documentation":"Raised by validate_webhook_url when httpx.URL rejects the webhook host as IDNA-invalid (httpx.InvalidURL), translated to ValueError because callers only guard ValueError. Without the translation an exotic/illegal hostname would escape as a 500 or crash the dispatch loop instead of failing closed as an unsafe webhook. It complements the urlparse check by validating the exact ASCII host httpx would connect to.","triggerScenarios":"A webhook URL whose hostname contains characters that cannot be IDNA-encoded — e.g. embedded underscores ('my_hook.example.com' is actually accepted by some resolvers but rejected here), labels >63 chars, prohibited code points, or stray unicode punctuation in the host.","commonSituations":"Hostnames with underscores common in internal naming (service_name.internal); copy-pasting a URL with a smart quote/zero-width character; very long auto-generated subdomains exceeding per-label limits.","solutions":["Fix the hostname to a valid DNS name: replace underscores with hyphens, remove stray unicode characters","Validate with httpx.URL(url) client-side — if it raises InvalidURL the server will reject it too","Register the webhook under a clean canonical name (CNAME to the odd internal host if needed)"],"exampleFix":"# before\nurl = \"https://my_hook.example.com/cb\"\n# after\nurl = \"https://my-hook.example.com/cb\"","handlingStrategy":"validation","validationCode":"import httpx\n\ndef webhook_host_idna_safe(url: str) -> bool:\n    try:\n        httpx.URL(url)\n        return True\n    except httpx.InvalidURL:\n        return False","typeGuard":null,"tryCatchPattern":"try:\n    await client.tasks.set_push_notification(task_id, cfg)\nexcept InvalidParamsError as e:\n    if \"invalid host\" in str(e):\n        cfg.url = sanitize_hostname(cfg.url)  # underscores->hyphens, strip unicode\n        await client.tasks.set_push_notification(task_id, cfg)\n    else:\n        raise","preventionTips":["Mirror the server: validate with httpx.URL before submitting any webhook","Ban underscores/unicode in webhook hostnames in your config linting","Use CNAMEs with clean names for oddly-named internal hosts"],"tags":["a2a","webhook","idna","url"],"backgroundTag":null,"analyzedSha":"976ec789d2886a86de109c044d089d68e96c9a35","analyzedAt":"2026-08-14T18:23:12.227Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}