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
- Use a URL that resolves to a genuinely public IP, or expose the receiver publicly (e.g. a tunnel with a public hostname)
- For local testing, run MLflow in an environment where this restriction is acceptable or use a test harness that stubs delivery
- Fix DNS so the hostname resolves to the intended public address; beware rebinding-resistant names
- 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
- Register webhooks only with public, non-rebinding hostnames
- Never point production webhooks at localhost/private IPs
- Test local receivers via a public tunnel (e.g. ngrok) instead
- Resolve the hostname before registering to catch split-horizon DNS surprises
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
- Could not determine webhook connection peer address: {e}
- Webhook connection resolved to an invalid IP address: {peer_
- MLFLOW_CRYPTO_KEK_PASSPHRASE environment variable must be se
- Failed to create KEK managers: {e}
- KEK rotation failed at secret '{secret.secret_name}'. No cha
AI-assisted analysis of mlflow/mlflow@6a27f2decc (2026-08-29).
Data as JSON: /api/errors/4f639a3664569225.
Report an issue: GitHub.