langflow-ai/langflow · error · InvalidParamsError
str(exc)
Error message
str(exc)
What it means
Raised by _SafePushConfigStore.set_info when registering an A2A task push-notification webhook whose URL fails validate_webhook_url. The ValueError (bad scheme, no host, blocked/private IP, IDNA-invalid host, or SSRF allowlist denial) is translated into the JSON-RPC InvalidParamsError so the client gets a spec-level error instead of a 500. This is the SSRF guard on a public, caller-controlled webhook URL, enforced at registration time (and re-checked at dispatch).
Source
Thrown at src/backend/base/langflow/api/v1/a2a.py:385
via ``get_info_for_dispatch`` and is unaffected.
"""
return f"{resolve_user_scope(context)}:{context.state.get('flow_id', '')}"
class _SafePushConfigStore(InMemoryPushNotificationConfigStore):
"""Push-notification config store that SSRF-guards the webhook URL at registration.
The webhook target is caller-controlled on a public endpoint, so reject one that
resolves to a private/loopback/link-local address before storing it (rather than
letting the sender POST there). In-memory + per-worker for now; durable cross-worker
push configs are a later slice (like the streaming queue manager).
"""
async def set_info(self, task_id: str, notification_config: pb.TaskPushNotificationConfig, context) -> None:
try:
await validate_webhook_url(notification_config.url)
except ValueError as exc:
raise InvalidParamsError(message=str(exc)) from exc
await super().set_info(task_id, notification_config, context)
class _SafePushNotificationSender(BasePushNotificationSender):
"""Re-validate + DNS-pin the webhook at dispatch, closing the registration-time rebind gap.
``_SafePushConfigStore`` validates at registration, but a host can re-resolve to a
private/metadata IP afterwards (DNS rebinding). Re-resolve here and pin the connection
to the just-validated IPs (via the shared SSRF transport) so a rebind can't land on an
internal address. A webhook that now resolves to a blocked address is dropped (logged),
matching the SDK's swallow-and-return-False on a failed send.
"""
async def _dispatch_notification(self, event, push_info, task_id) -> bool:
url = push_info.url
try:
validated_ips = await validate_webhook_url(url)
except ValueError:View on GitHub (pinned to 976ec789d2)
Solutions
- Use a public https:// webhook URL that resolves to a globally routable IP
- For trusted internal networks, set LANGFLOW_A2A_ALLOW_PRIVATE_WEBHOOKS=true on the server (it skips the IP check)
- Fix malformed URLs: include scheme http/https and a real hostname; avoid IP-literal private addresses
Example fix
# before config.url = "http://localhost:9000/a2a-hook" # after config.url = "https://hooks.example.com/a2a-hook"
Defensive patterns
Strategy: validation
Validate before calling
from urllib.parse import urlparse
import ipaddress, socket
def webhook_url_safe(url: str) -> bool:
p = urlparse(url)
if p.scheme not in ("http", "https") or not p.hostname:
return False
try:
infos = socket.getaddrinfo(p.hostname, None)
except socket.gaierror:
return False
return all(
not ipaddress.ip_address(i[4][0]).is_private
and not ipaddress.ip_address(i[4][0]).is_loopback
and not ipaddress.ip_address(i[4][0]).is_link_local
for i in infos
) Try / catch
try:
await client.tasks.set_push_notification(task_id, config)
except InvalidParamsError as e:
if "webhook url" in str(e):
config.url = public_tunnel_url() # replace internal URL with public https
await client.tasks.set_push_notification(task_id, config)
else:
raise Prevention
- Register only public https webhook URLs in shared/production deployments
- Keep a client-side pre-check mirroring the server rules: scheme, host, resolved-IP policy
- For internal receivers use a tunnel; only flip LANGFLOW_A2A_ALLOW_PRIVATE_WEBHOOKS on trusted networks
When it happens
Trigger: A2A message/send or tasks/pushNotificationConfig/set with notification_config.url pointing at e.g. http://localhost:9000/hook, http://169.254.169.254/..., ftp://..., a URL with no hostname, or a host that resolves to a private/CGNAT range when SSRF protection is on.
Common situations: Local testing with a loopback webhook against a hardened server; pointing webhooks at internal hostnames; typos like 'http:/host' (missing slash yields no host); DNS entries that resolve to 10.x/172.16.x/192.168.x ranges.
Related errors
- webhook url resolves to a blocked address: {', '.join(blocke
- webhook url is not allowed: {exc}
- Task {params.id} has no active stream to resubscribe to
- webhook url must be http or https
- webhook url has no host
AI-assisted analysis of langflow-ai/langflow@976ec789d2 (2026-08-14).
Data as JSON: /api/errors/be8d51e78b5bc21b.
Report an issue: GitHub.