{"record":{"id":"f1902dcaf5cd7716","repo":"siyuan-note/siyuan","slug":"generated-image-url-resolved-to-a-private-or-inval","errorCode":null,"errorMessage":"generated image URL resolved to a private or invalid IP","messagePattern":"generated image URL resolved to a private or invalid IP","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/openai.go","lineNumber":986,"sourceCode":"\t\t\tif len(via) >= 3 || req.URL.Scheme != \"https\" {\n\t\t\t\treturn errors.New(\"generated image redirect is not allowed\")\n\t\t\t}\n\t\t\treturn CheckHostSSRF(req.URL.Hostname())\n\t\t},\n\t}\n}\n\nfunc generatedImageDialer() *net.Dialer {\n\treturn &net.Dialer{\n\t\tTimeout: 30 * time.Second,\n\t\tControl: func(_, address string, _ syscall.RawConn) error {\n\t\t\thost, _, err := net.SplitHostPort(address)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tip, parseErr := netip.ParseAddr(host)\n\t\t\tif parseErr != nil || isUnsafeGeneratedImageIP(ip.Unmap()) {\n\t\t\t\treturn errors.New(\"generated image URL resolved to a private or invalid IP\")\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n}\n\nfunc isUnsafeGeneratedImageIP(ip netip.Addr) bool {\n\tif !ip.IsValid() || !ip.IsGlobalUnicast() || ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsUnspecified() {\n\t\treturn true\n\t}\n\t// IsPrivate 不包含共享地址空间和基准测试网段，这些地址仍可能指向本地基础设施。\n\tfor _, prefix := range []netip.Prefix{\n\t\tnetip.MustParsePrefix(\"100.64.0.0/10\"),\n\t\tnetip.MustParsePrefix(\"198.18.0.0/15\"),\n\t} {\n\t\tif prefix.Contains(ip) {\n\t\t\treturn true\n\t\t}","sourceCodeStart":968,"sourceCodeEnd":1004,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/openai.go#L968-L1004","documentation":"The download uses a custom DialContext (Control/dialer) that resolves the host and inspects the resulting IP with netip.ParseAddr + isUnsafeGeneratedImageIP. If the host does not resolve to a parseable public IP, or resolves to a private/loopback/link-local/invalid address, the connection is refused with this error. This prevents SSRF: a provider-controlled URL must not be able to reach the machine's own network.","triggerScenarios":"The image URL hostname resolves to 127.0.0.1, 10.x/172.16.x/192.168.x, ::1, link-local 169.254.x, or fails to resolve (ParseAddr/lookup error) when the dialer connects.","commonSituations":"Provider returning a URL pointing at localhost or a LAN host (malicious or misconfigured); DNS rebinding to a private IP; DNS resolution failure in an offline/air-gapped environment; IPv6-only host with an address the check treats as unsafe; typo'd internal hostname in a self-hosted proxy config.","solutions":["Ensure the image host is a public DNS name resolving to a public IP","Fix DNS so the hostname resolves (check resolv.conf / network connectivity)","If using a self-hosted image service on the LAN, expose it via a public HTTPS endpoint instead of a private IP URL","Check the resolved IPs with `dig <host>`; if private, the provider response is untrustworthy — regenerate the image"],"exampleFix":"// before\nurl := \"https://intranet.local/img.png\" // resolves to 192.168.1.10 -> blocked\n// after\nurl := \"https://cdn.example.com/img.png\" // public IP -> allowed","handlingStrategy":"validation","validationCode":"ip, err := netip.ParseAddr(host)\nif err != nil || ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsUnspecified() {\n    return errors.New(\"host resolves to unsafe IP\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only accept image URLs on public DNS names resolving to public IPs","Resolve and check IPs at dial time (not just at URL-parse time) to defeat DNS rebinding","Monitor DNS failures as a separate signal from SSRF blocks"],"tags":["security","ssrf","dns","network"],"backgroundTag":"ssrf-protection-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"}