{"record":{"id":"51bc36fe48106743","repo":"micro/go-micro","slug":"push-callback-cannot-parse-dial-address-q","errorCode":null,"errorMessage":"push callback: cannot parse dial address %q","messagePattern":"push callback: cannot parse dial address %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gateway/a2a/pushsecurity.go","lineNumber":88,"sourceCode":"\t\tip.IsPrivate() ||\n\t\tip.IsLinkLocalUnicast() ||\n\t\tip.IsLinkLocalMulticast() ||\n\t\tip.IsInterfaceLocalMulticast() ||\n\t\tip.IsMulticast() ||\n\t\tip.IsUnspecified()\n}\n\n// pushDialControl runs after DNS resolution, immediately before connect, on the\n// resolved address — so it blocks a host that passed URL validation but was\n// rebound to an internal IP (DNS rebinding).\nfunc pushDialControl(_, address string, _ syscall.RawConn) error {\n\thost, _, err := net.SplitHostPort(address)\n\tif err != nil {\n\t\treturn err\n\t}\n\tip := net.ParseIP(host)\n\tif ip == nil {\n\t\treturn fmt.Errorf(\"push callback: cannot parse dial address %q\", address)\n\t}\n\tif blockedPushIP(ip) {\n\t\treturn fmt.Errorf(\"push callback: refusing to connect to blocked address %s\", ip)\n\t}\n\treturn nil\n}\n\n// pushGuardClient is the HTTP client used for default-policy push delivery. Its\n// dialer refuses connections to blocked addresses at connect time.\nvar pushGuardClient = &http.Client{\n\tTimeout: 10 * time.Second,\n\tTransport: &http.Transport{\n\t\tProxy: http.ProxyFromEnvironment,\n\t\tDialContext: (&net.Dialer{\n\t\t\tTimeout: 5 * time.Second,\n\t\t\tControl: pushDialControl,\n\t\t}).DialContext,\n\t},","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/micro/go-micro/blob/24529f140421a11a33b6999ab7944f2021cfd69c/gateway/a2a/pushsecurity.go#L70-L106","documentation":"pushDialControl validates the address a push-callback HTTP request is about to dial. It splits the address into host and port and parses the host as an IP; if the host is not a valid IP literal (e.g. a hostname like 'example.com:8080'), it rejects the dial. This guard ensures push callbacks only ever connect to explicit IP addresses that the SSRF policy can evaluate.","triggerScenarios":"Delivering a push notification whose resolved dial address has a host part that is a DNS name rather than an IP literal, so net.SplitHostPort succeeds but net.ParseIP returns nil.","commonSituations":"Configuring a push callback URL with a hostname (https://worker.example.com/callback) instead of an IP; a custom AllowPushURL policy permitting hostnames but the dialer requiring IPs; DNS-based service names in the callback URL.","solutions":["Use an IP-literal callback URL (e.g. http://10.0.0.5:8080/callback) instead of a hostname.","Resolve the hostname to an IP beforehand if a custom dialer/policy is in place, and configure the callback with the resolved IP.","If hostname callbacks are legitimately required, supply a custom dial control / AllowPushURL policy that resolves and validates hostnames before dialing."],"exampleFix":"// before\nconfig.PushCallbackURL = \"https://worker.example.com/callback\"\n// after\nconfig.PushCallbackURL = \"https://203.0.113.10/callback\" // IP literal accepted by pushDialControl","handlingStrategy":"validation","validationCode":"host, _, err := net.SplitHostPort(strings.TrimPrefix(callbackURL, \"http://\"))\nif err != nil || net.ParseIP(host) == nil {\n    return fmt.Errorf(\"push callback host must be an IP literal, got %q\", host)\n}","typeGuard":"func isIPLiteral(rawURL string) bool {\n    u, err := url.Parse(rawURL)\n    if err != nil { return false }\n    host := u.Hostname()\n    return net.ParseIP(host) != nil\n}","tryCatchPattern":null,"preventionTips":["Always use IP-literal push callback URLs under the default policy.","Pre-resolve hostnames to IPs before configuring callbacks.","Test callback configuration with the same dial-control used at delivery time."],"tags":["ssrf","push-callback","network","url-validation"],"backgroundTag":"invalid-dial-address","analyzedSha":"24529f140421a11a33b6999ab7944f2021cfd69c","analyzedAt":"2026-09-01T02:52:24.923Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}