mlflow/mlflow · error · SSRFProtectionError

Webhook connection blocked: {ip} is not a public IP address.

Error message

Webhook connection blocked: {ip} is not a public IP address. This may indicate a DNS rebinding attempt.

What it means

The webhook's target resolved to a non-public IP (loopback, private RFC1918, link-local, etc.). MLflow blocks it as a likely DNS-rebinding / SSRF attempt and closes the connection. This is intentional security behavior, not a bug.

Source

Thrown at mlflow/webhooks/ssrf.py:63

    # It can raise OSError (e.g. ENOTCONN) on an unconnected socket; fail closed.
    try:
        peer_ip = sock.getpeername()[0]
    except OSError as e:
        sock.close()
        raise SSRFProtectionError(
            f"Could not determine webhook connection peer address: {e}"
        ) from e
    try:
        ip = ipaddress.ip_address(peer_ip)
    except ValueError as e:
        sock.close()
        raise SSRFProtectionError(
            f"Webhook connection resolved to an invalid IP address: {peer_ip!r}"
        ) from e

    if not ip.is_global:
        sock.close()
        raise SSRFProtectionError(
            f"Webhook connection blocked: {ip} is not a public IP address. "
            "This may indicate a DNS rebinding attempt."
        )


class _SSRFProtectedHTTPConnection(HTTPConnection):
    def _new_conn(self) -> socket.socket:
        sock = super()._new_conn()
        _assert_public_peer(sock)
        return sock


class _SSRFProtectedHTTPSConnection(HTTPSConnection):
    def _new_conn(self) -> socket.socket:
        # HTTPSConnection inherits _new_conn from HTTPConnection: it returns the
        # raw TCP socket before the TLS handshake, so the IP check runs pre-TLS.
        sock = super()._new_conn()
        _assert_public_peer(sock)

View on GitHub (pinned to 6a27f2decc)

Solutions

  1. Use a URL that resolves to a genuinely public IP, or expose the receiver publicly (e.g. a tunnel with a public hostname)
  2. For local testing, run MLflow in an environment where this restriction is acceptable or use a test harness that stubs delivery
  3. Fix DNS so the hostname resolves to the intended public address; beware rebinding-resistant names
  4. If internal delivery is a legitimate requirement, it needs an allowlist change in MLflow's SSRF policy — file an issue rather than disabling the check

Example fix

// before
{"url": "http://localhost:9000/hook"}  # blocked
// after
{"url": "https://hooks.example.com/mlflow"}  # public IP
Defensive patterns

Strategy: validation

Validate before calling

import ipaddress, socket
url_host = "hooks.example.com"
ip = ipaddress.ip_address(socket.gethostbyname(url_host))
assert ip.is_global, f"{url_host} resolves to non-public {ip}; webhook will be blocked"

Prevention

When it happens

Trigger: Registering a webhook whose URL points at localhost, 127.0.0.1, 10.x/172.16-31.x/192.168.x, 169.254.x (metadata endpoints), or a DNS name that rebinding-resolves to such an address at connection time.

Common situations: Testing webhooks against a local receiver (localhost) on a production-configured MLflow server; DNS rebinding hostname; internal-only service URLs; split-horizon DNS resolving externally-registered names to private IPs.

Related errors


AI-assisted analysis of mlflow/mlflow@6a27f2decc (2026-08-29). Data as JSON: /api/errors/4f639a3664569225. Report an issue: GitHub.