{"record":{"id":"446d29335e26dbc2","repo":"siyuan-note/siyuan","slug":"host-has-no-public-ip-s","errorCode":null,"errorMessage":"host has no public IP: %s","messagePattern":"host has no public IP: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/util/net.go","lineNumber":228,"sourceCode":"\t\tips, err := net.DefaultResolver.LookupIPAddr(ctx, host)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tvar lastErr error\n\t\tfor _, ipAddr := range ips {\n\t\t\tif isPrivateIP(ipAddr.IP) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconn, err := dialer.DialContext(ctx, network, net.JoinHostPort(ipAddr.IP.String(), port))\n\t\t\tif err == nil {\n\t\t\t\treturn conn, nil\n\t\t\t}\n\t\t\tlastErr = err\n\t\t}\n\t\tif lastErr != nil {\n\t\t\treturn nil, lastErr\n\t\t}\n\t\treturn nil, errors.New(\"host has no public IP: \" + host)\n\t}\n}\n\n// isPrivateIP 判断 IP 是否为私网地址，含内嵌私网 IPv4 的 IPv6 过渡地址（NAT64、6to4、Teredo、IPv4 兼容）。\n// https://github.com/siyuan-note/siyuan/security/advisories/GHSA-qq8m-8p8v-x4xg\n// https://github.com/siyuan-note/siyuan/security/advisories/GHSA-rg26-cg95-gq6p\nfunc isPrivateIP(ip net.IP) bool {\n\tif ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() ||\n\t\tip.IsPrivate() || ip.IsUnspecified() || ip.IsMulticast() {\n\t\treturn true\n\t}\n\t// Go 标准库的分类方法不识别 IPv6 过渡地址，需按 RFC 内嵌格式提取其中的 IPv4 后再递归判断。\n\tif ip4 := extractEmbeddedIPv4(ip); nil != ip4 && !ip4.Equal(ip) {\n\t\treturn isPrivateIP(ip4)\n\t}\n\treturn false\n}\n","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/net.go#L210-L246","documentation":"In ssrfSafeDialContext, when a hostname resolves but every resolved IP is private (all candidates skipped), and no prior dial error was recorded, the dial fails with \"host has no public IP: %s\". This guarantees the agent can only reach genuinely public endpoints, closing DNS-rebinding attacks where a public hostname resolves to internal addresses.","triggerScenarios":"Dialing a hostname through ssrfSafeDialContext whose DNS records contain only private IPs — e.g. internal names like myservice.local, split-horizon DNS returning 192.168.x.x for a public-looking name, or DNS rebinding (rebind.tools-style) hostnames.","commonSituations":"Using a LAN-only DNS name in an agent URL while off the corporate network; a domain configured in split-horizon DNS; a hostname whose A/AAAA records all point to loopback/private ranges; malicious rebinding domains.","solutions":["Use a hostname that resolves to at least one public IP for agent outbound requests","Resolve the domain externally (dig +short @8.8.8.8) to inspect what IPs it returns and fix the DNS records if wrong","If the service must stay internal, it is unreachable by design from this guarded path — expose a public endpoint instead","If this appears unexpectedly, check for DNS hijacking/rebinding — treat it as a security signal rather than working around it"],"exampleFix":"// before\nurl := \"https://internal.company.dev/api\" // resolves only to 10.0.0.3 -> host has no public IP\n// after: publish the service with a public A record (or use the public gateway)\nurl := \"https://api.company.com/internal-proxy\"","handlingStrategy":"validation","validationCode":"// Go: resolve the hostname first and confirm it has a public IP\nips, err := net.DefaultResolver.LookupIPAddr(context.Background(), host)\nif err == nil {\n    hasPublic := false\n    for _, a := range ips { if !isLikelyPrivate(a.IP) { hasPublic = true } }\n    if !hasPublic { return fmt.Errorf(\"%s resolves only to private IPs\", host) }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Resolve hostnames with an external resolver to verify they return public IPs before use","Avoid internal-only DNS names in agent-facing configuration","Treat 'host has no public IP' as a possible DNS-rebinding signal and investigate rather than bypass","Use managed public endpoints for internal services instead of raw LAN DNS names"],"tags":["go","dns","ssrf","security"],"backgroundTag":"private-ip-blocked","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}