langflow-ai/langflow · error · ValueError

webhook url is not allowed: {exc}

Error message

webhook url is not allowed: {exc}

What it means

Raised by validate_webhook_url when the framework-level SSRF validation (validate_and_resolve_url) rejects the URL with SSRFProtectionError — e.g. the host is not on the configured allowlist, or it resolves into CGNAT/other space the framework blocks beyond the hard floor. This check supplies the allowlist/global-toggle policy on top of the private-IP floor, and the error chains the original framework message.

Source

Thrown at src/backend/base/langflow/api/v1/a2a_utils.py:135

        host = webhook_pin_host(url)
        # Hard floor: reject private/metadata IPs even when global SSRF protection is off
        # (validate_and_resolve_url returns [] with NO enforcement in that case).
        # resolve_hostname handles IP-literal hosts too; the blocking resolve runs off-loop.
        floor_ips = await asyncio.to_thread(resolve_hostname, host)
        blocked = [ip for ip in floor_ips if is_ip_blocked(ip)]
        if blocked:
            msg = f"webhook url resolves to a blocked address: {', '.join(blocked)}"
            raise ValueError(msg)
        # Then the framework check for allowlist / CGNAT / is_global extras + pinned IPs.
        _url, validated_ips = await asyncio.to_thread(validate_and_resolve_url, url)
    except httpx.InvalidURL as exc:
        # Callers only guard ValueError; without translating this an IDNA-invalid host would 500 the
        # caller (or escape dispatch) instead of failing closed as an unsafe webhook.
        msg = f"webhook url has an invalid host: {exc}"
        raise ValueError(msg) from exc
    except SSRFProtectionError as exc:
        msg = f"webhook url is not allowed: {exc}"
        raise ValueError(msg) from exc
    # Fall back to the floor IPs so dispatch can still DNS-pin with the global toggle off.
    return validated_ips or floor_ips


async def folder_auth_type(flow: Flow, session: AsyncSession) -> str:
    """Read the flow's folder ``auth_type`` (``"none"`` | ``"apikey"`` | ``"oauth"``).

    The single source of truth for what the card advertises (resolve_card_security)
    and what the JSON-RPC route enforces, so the two can't drift. Plaintext read,
    no decrypt. No folder / missing folder -> ``"none"`` (public).
    """
    if flow.folder_id is None:
        return "none"
    # Query the folder explicitly; lazy-loading flow.folder would raise in async.
    folder = (await session.exec(select(Folder).where(Folder.id == flow.folder_id))).first()
    return (folder.auth_settings or {}).get("auth_type", "none") if folder else "none"

View on GitHub (pinned to 976ec789d2)

Solutions

  1. Add the webhook host to the server's SSRF allowlist configuration and retry
  2. Use a webhook host that the framework classifies as globally routable and allowlisted
  3. If policy allows, relax the framework SSRF setting — but keep the private-IP floor in mind (it is independent)

Example fix

# before: host hooks.vendor.io not allowlisted
# after (server env): LANGFLOW_SSRF_ALLOWLIST=hooks.vendor.io
Defensive patterns

Strategy: fallback

Validate before calling

def webhook_host_allowlisted(url: str, allowlist: set[str]) -> bool:
    from urllib.parse import urlparse
    return (urlparse(url).hostname or "") in allowlist

Try / catch

try:
    await client.tasks.set_push_notification(task_id, cfg)
except InvalidParamsError as e:
    if "not allowed" in str(e):
        cfg.url = ALLOWLISTED_HOOK_URL   # fall back to the vetted receiver
        await client.tasks.set_push_notification(task_id, cfg)
    else:
        raise

Prevention

When it happens

Trigger: SSRF protection enabled with an allowlist that does not include the webhook host; the URL resolves to 100.64.0.0/10 (CGNAT) or another range the framework flags as non-global even though it is not in the basic private floor.

Common situations: Hardened deployments that set LANGFLOW_SSRF_ALLOWLIST (or equivalent) so only vetted webhook hosts are permitted; webhooks pointing at shared cloud egress IPs that map into CGNAT ranges.

Related errors


AI-assisted analysis of langflow-ai/langflow@976ec789d2 (2026-08-14). Data as JSON: /api/errors/42e1042dc91832dc. Report an issue: GitHub.