siyuan-note/siyuan · error
generated image URL resolved to a private or invalid IP
Error message
generated image URL resolved to a private or invalid IP
What it means
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.
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
Example fix
// before url := "https://intranet.local/img.png" // resolves to 192.168.1.10 -> blocked // after url := "https://cdn.example.com/img.png" // public IP -> allowed
Defensive patterns
Strategy: validation
Validate before calling
ip, err := netip.ParseAddr(host)
if err != nil || ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsUnspecified() {
return errors.New("host resolves to unsafe IP")
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- failed to resolve host:
- host has no public IP:
- access to private/internal IP is prohibited
- access to private/internal IP is prohibited
- host has no public IP
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/f1902dcaf5cd7716.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/openai.go:986
if len(via) >= 3 || req.URL.Scheme != "https" {
return errors.New("generated image redirect is not allowed")
}
return CheckHostSSRF(req.URL.Hostname())
},
}
}
func generatedImageDialer() *net.Dialer {
return &net.Dialer{
Timeout: 30 * time.Second,
Control: func(_, address string, _ syscall.RawConn) error {
host, _, err := net.SplitHostPort(address)
if err != nil {
return err
}
ip, parseErr := netip.ParseAddr(host)
if parseErr != nil || isUnsafeGeneratedImageIP(ip.Unmap()) {
return errors.New("generated image URL resolved to a private or invalid IP")
}
return nil
},
}
}
func isUnsafeGeneratedImageIP(ip netip.Addr) bool {
if !ip.IsValid() || !ip.IsGlobalUnicast() || ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsUnspecified() {
return true
}
// IsPrivate 不包含共享地址空间和基准测试网段,这些地址仍可能指向本地基础设施。
for _, prefix := range []netip.Prefix{
netip.MustParsePrefix("100.64.0.0/10"),
netip.MustParsePrefix("198.18.0.0/15"),
} {
if prefix.Contains(ip) {
return true
}View on GitHub (pinned to 9f775e8a12)