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
Thrown by the Control callback of generatedImageDialer (kernel/util/openai.go:839) at socket-connect time, when the resolved IP of the image host is not a safe public unicast address. isUnsafeGeneratedImageIP rejects private (RFC1918), loopback, link-local, unspecified, non-global-unicast, plus the shared (100.64.0.0/10) and benchmark (198.18.0.0/15) ranges. It is the SSRF defense-of-depth layer below CheckHostSSRF, blocking DNS rebinding and IPs that resolve to internal infrastructure.
Source
Thrown at kernel/util/openai.go:839
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 251596fc0d)
Solutions
- Use only reputable image providers whose CDN resolves to public unicast IPs.
- Switch to b64_json delivery so no outbound fetch to the URL is attempted.
- If self-hosting the provider, ensure its image host announces public IPs and there is no split-horizon DNS.
- Do not point the model at user-supplied image URLs.
Example fix
// before
// adapter relies on URL delivery; provider host intermittently resolves internally
data, err = downloadGeneratedImage(requestCtx, result.URL)
// after (force base64 delivery so no dial is made)
if strings.HasPrefix(strings.ToLower(adapter.model), "dall-e") {
imageRequest.ResponseFormat = openai.CreateImageResponseFormatB64JSON
} Defensive patterns
Strategy: fallback
Validate before calling
// Strategic pre-check: resolve the host yourself and reject private IPs before dialing
host := u.Hostname()
ips, err := net.LookupHost(host)
if err != nil {
return fmt.Errorf("cannot resolve image host %s: %s", host, err)
}
for _, ip := range ips {
addr := netip.MustParseAddr(ip)
if !addr.IsGlobalUnicast() || addr.IsPrivate() || addr.IsLoopback() || addr.IsLinkLocalUnicast() {
return fmt.Errorf("image host %s resolves to unsafe IP %s", host, ip)
}
} Try / catch
data, err := downloadGeneratedImage(ctx, result.URL)
if err != nil && strings.Contains(err.Error(), "private or invalid IP") {
// SSRF guard fired; do NOT bypass it. Switch to b64_json or refuse the provider.
logging.LogErrorf("refusing image URL that resolves internally: %s", result.URL)
imageRequest.ResponseFormat = openai.CreateImageResponseFormatB64JSON
// ...re-issue CreateImage
}
if err != nil { return err } Prevention
- Only point image models at reputable providers whose CDN uses public unicast IPs.
- Default to b64_json to avoid the dial-time SSRF surface entirely.
- Never feed user-supplied image URLs into this path.
- Do NOT weaken isUnsafeGeneratedImageIP to work around this — the guard is correct.
When it happens
Trigger: The image URL's hostname resolves to a private/loopback/link-local IP at connect time; DNS rebinding makes the hostname resolve to a public IP at the SSRF-check stage but a private IP at dial time; the URL points at a hostname that fails to parse as a valid netip.Addr.
Common situations: Provider misconfiguration serving images from an internal NAT address; an attacker-crafted image URL targeting internal services; a test environment pointing at localhost; DNS that occasionally returns internal views.
Related errors
- generated image URL must use HTTPS
- generated image redirect is not allowed
- ip address [%s] is prohibited
- generated image is empty
- generated image exceeds size limit
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/f1902dcaf5cd7716.
Report an issue: GitHub.