{"record":{"id":"4f639a3664569225","repo":"mlflow/mlflow","slug":"webhook-connection-blocked-ip-is-not-a-public-i","errorCode":null,"errorMessage":"Webhook connection blocked: {ip} is not a public IP address. This may indicate a DNS rebinding attempt.","messagePattern":"Webhook connection blocked: (.+?) is not a public IP address\\. This may indicate a DNS rebinding attempt\\.","errorType":"exception","errorClass":"SSRFProtectionError","httpStatus":null,"severity":"error","filePath":"mlflow/webhooks/ssrf.py","lineNumber":63,"sourceCode":"    # It can raise OSError (e.g. ENOTCONN) on an unconnected socket; fail closed.\n    try:\n        peer_ip = sock.getpeername()[0]\n    except OSError as e:\n        sock.close()\n        raise SSRFProtectionError(\n            f\"Could not determine webhook connection peer address: {e}\"\n        ) from e\n    try:\n        ip = ipaddress.ip_address(peer_ip)\n    except ValueError as e:\n        sock.close()\n        raise SSRFProtectionError(\n            f\"Webhook connection resolved to an invalid IP address: {peer_ip!r}\"\n        ) from e\n\n    if not ip.is_global:\n        sock.close()\n        raise SSRFProtectionError(\n            f\"Webhook connection blocked: {ip} is not a public IP address. \"\n            \"This may indicate a DNS rebinding attempt.\"\n        )\n\n\nclass _SSRFProtectedHTTPConnection(HTTPConnection):\n    def _new_conn(self) -> socket.socket:\n        sock = super()._new_conn()\n        _assert_public_peer(sock)\n        return sock\n\n\nclass _SSRFProtectedHTTPSConnection(HTTPSConnection):\n    def _new_conn(self) -> socket.socket:\n        # HTTPSConnection inherits _new_conn from HTTPConnection: it returns the\n        # raw TCP socket before the TLS handshake, so the IP check runs pre-TLS.\n        sock = super()._new_conn()\n        _assert_public_peer(sock)","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/mlflow/mlflow/blob/6a27f2decc0b76eb1b54af31849784addb357dbc/mlflow/webhooks/ssrf.py#L45-L81","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\n{\"url\": \"http://localhost:9000/hook\"}  # blocked\n// after\n{\"url\": \"https://hooks.example.com/mlflow\"}  # public IP","handlingStrategy":"validation","validationCode":"import ipaddress, socket\nurl_host = \"hooks.example.com\"\nip = ipaddress.ip_address(socket.gethostbyname(url_host))\nassert ip.is_global, f\"{url_host} resolves to non-public {ip}; webhook will be blocked\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["security","ssrf","webhooks","dns"],"backgroundTag":"ssrf-protection-blocked","analyzedSha":"6a27f2decc0b76eb1b54af31849784addb357dbc","analyzedAt":"2026-08-29T20:54:51.419Z","schemaVersion":2},"datasetVersion":"2026-08-29T22:17:34.462Z"}