{"record":{"id":"8ea0353ad0405156","repo":"micro/go-micro","slug":"push-callback-url-has-no-host","errorCode":null,"errorMessage":"push callback url has no host","messagePattern":"push callback url has no host","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gateway/a2a/pushsecurity.go","lineNumber":40,"sourceCode":"// that passes validation cannot be rebound to an internal address before the\n// connection is made. Operators who need to reach a trusted in-cluster\n// receiver set Options.AllowPushURL to take over the policy.\n\n// 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 {","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/micro/go-micro/blob/24529f140421a11a33b6999ab7944f2021cfd69c/gateway/a2a/pushsecurity.go#L22-L58","documentation":"defaultPushURLPolicy throws this error when the parsed push callback URL has an empty host component. A URL like 'https:///hook' or a relative path has nowhere to deliver push notifications, so the policy rejects it before any resolution or network use. Note the hostname() call strips the port, so a host must be a real DNS name or IP.","triggerScenarios":"Calling SetPushNotificationConfig with a URL whose authority is missing: 'https:///path', 'http://:8080/hook', or a bare path like '/webhook' that url.Parse accepts without error.","commonSituations":"Template/config substitution leaving an empty hostname (e.g. unset HOST env var interpolated into the URL); hand-built URL strings via concatenation; forgetting the domain when pasting a webhook.","solutions":["Include a full authority in the callback URL: scheme://host[:port]/path.","Check the environment/config value feeding the host portion of the URL (e.g. PUBLIC_HOST is unset).","Pre-validate with url.Parse and require u.Hostname() != \"\" before calling the library."],"exampleFix":"// before\nu := os.Getenv(\"PUBLIC_HOST\") // \"\"\ncallback := \"https://\" + u + \"/a2a/push\"\n// after\nif u == \"\" { return errors.New(\"PUBLIC_HOST must be set\") }\ncallback := \"https://\" + u + \"/a2a/push\"","handlingStrategy":"validation","validationCode":"u, err := url.Parse(callback)\nif err != nil || u.Hostname() == \"\" {\n\treturn fmt.Errorf(\"callback must include a host: %q\", callback)\n}","typeGuard":"func hasHost(u *url.URL) bool {\n\treturn u != nil && u.Hostname() != \"\"\n}","tryCatchPattern":null,"preventionTips":["Fail fast at config load if PUBLIC_HOST/base-URL env vars are empty","Build URLs with url.URL struct fields instead of string concatenation","Test webhook URL templates with empty-variable substitution"],"tags":["validation","url","push-notifications"],"backgroundTag":"invalid-url-scheme","analyzedSha":"24529f140421a11a33b6999ab7944f2021cfd69c","analyzedAt":"2026-09-01T02:52:24.923Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}