{"record":{"id":"5bbfa7af2df85d63","repo":"micro/go-micro","slug":"push-callback-host-q-w","errorCode":null,"errorMessage":"push callback host %q: %w","messagePattern":"push callback host %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gateway/a2a/pushsecurity.go","lineNumber":44,"sourceCode":"// pushLookupIP resolves a host to IPs; overridable in tests.\nvar pushLookupIP = net.LookupIP\n\n// defaultPushURLPolicy is the SSRF-safe policy applied when no AllowPushURL is\n// configured. It rejects non-http(s) schemes and hosts that resolve to a\n// loopback, private, link-local, multicast, or unspecified address.\nfunc defaultPushURLPolicy(u *url.URL) error {\n\tswitch u.Scheme {\n\tcase \"http\", \"https\":\n\tdefault:\n\t\treturn fmt.Errorf(\"push callback scheme %q not allowed (want http or https)\", u.Scheme)\n\t}\n\thost := u.Hostname()\n\tif host == \"\" {\n\t\treturn fmt.Errorf(\"push callback url has no host\")\n\t}\n\tips, err := resolvePushHost(host)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"push callback host %q: %w\", host, err)\n\t}\n\tif len(ips) == 0 {\n\t\treturn fmt.Errorf(\"push callback host %q did not resolve\", host)\n\t}\n\tfor _, ip := range ips {\n\t\tif blockedPushIP(ip) {\n\t\t\treturn fmt.Errorf(\"push callback host %q resolves to a blocked address %s\", host, ip)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc resolvePushHost(host string) ([]net.IP, error) {\n\tif ip := net.ParseIP(host); ip != nil {\n\t\treturn []net.IP{ip}, nil\n\t}\n\treturn pushLookupIP(host)\n}","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/micro/go-micro/blob/24529f140421a11a33b6999ab7944f2021cfd69c/gateway/a2a/pushsecurity.go#L26-L62","documentation":"After checking the scheme and host, defaultPushURLPolicy resolves the callback host via resolvePushHost. This error wraps any DNS/system resolution failure (%w), such as NXDOMAIN or resolver timeouts, prefixed with the host being resolved. It indicates the callback host could not be turned into IP addresses, so the SSRF policy cannot evaluate it.","triggerScenarios":"Calling SetPushNotificationConfig with a callback hostname that DNS cannot resolve: typo in the domain, internal-only name used from a network without that DNS zone, or the resolver is down.","commonSituations":"Registering webhooks with a hostname valid only inside the agent's private network while the gateway uses public DNS; DNS outage; host string includes a scheme or port by mistake (e.g. 'https://example.com' passed as host).","solutions":["Confirm the hostname resolves: run nslookup/dig on the exact host from the environment running the gateway.","Fix typos or stray scheme/port characters in the callback URL.","If the host is internal-only, ensure the gateway's DNS configuration can resolve it.","Retry on transient resolver failures (the wrapped error distinguishes timeouts from NXDOMAIN)."],"exampleFix":"// before\ncallback := \"https://webhooks.internal-net.local/hook\" // NXDOMAIN from gateway DNS\n// after\ncallback := \"https://webhooks.internal.example.com/hook\" // resolvable via configured DNS","handlingStrategy":"validation","validationCode":"if _, err := net.LookupHost(host); err != nil {\n\treturn fmt.Errorf(\"callback host %q does not resolve: %w\", host, err)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Verify webhook hostnames resolve from the deployment environment before registering","Avoid internal-only DNS names for callbacks consumed by the gateway","Check for stray scheme/port text in host values","Retry registration on transient resolver timeouts only"],"tags":["dns","network","push-notifications","validation"],"backgroundTag":"dns-resolution-failed","analyzedSha":"24529f140421a11a33b6999ab7944f2021cfd69c","analyzedAt":"2026-09-01T02:52:24.923Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}