{"record":{"id":"7ac31172a33e3132","repo":"siyuan-note/siyuan","slug":"generated-image-redirect-is-not-allowed","errorCode":null,"errorMessage":"generated image redirect is not allowed","messagePattern":"generated image redirect is not allowed","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/openai.go","lineNumber":822,"sourceCode":"\tdata, err := io.ReadAll(io.LimitReader(resp.Body, maxGeneratedImageBytes+1))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(data) > maxGeneratedImageBytes {\n\t\treturn nil, errors.New(\"generated image exceeds size limit\")\n\t}\n\treturn data, nil\n}\n\nfunc generatedImageHTTPClient() *http.Client {\n\treturn &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tProxy:       http.ProxyFromEnvironment,\n\t\t\tDialContext: generatedImageDialer().DialContext,\n\t\t},\n\t\tCheckRedirect: func(req *http.Request, via []*http.Request) error {\n\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}","sourceCodeStart":804,"sourceCodeEnd":840,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/util/openai.go#L804-L840","documentation":"Thrown inside generatedImageHTTPClient's CheckRedirect (kernel/util/openai.go:822) when an HTTP redirect chain either exceeds 2 hops (len(via) >= 3 means this is the 4th request) or a redirect target uses a non-https scheme. It hardens image downloads against redirect-based SSRF and downgrade attacks.","triggerScenarios":"The image URL redirects more than 3 times; a redirect lands on an http:// URL; a redirect loop forms. The hook is invoked by net/http on every 3xx response.","commonSituations":"Provider CDN chains through multiple regional redirects; a middleware rewrites a redirect to http; a misconfigured load balancer creates a loop; an attacker-controlled URL tries to bounce through redirects to internal hosts.","solutions":["Switch the request to b64_json so no URL fetch / redirect occurs.","If you operate the CDN, flatten the redirect chain and keep every hop on https.","Verify the returned URL is the final asset URL, not a short-link that bounces."],"exampleFix":"// before\n// request relies on URL delivery; provider returns multi-hop redirects\nimageRequest.ResponseFormat = \"\"  // provider picks URL\n\n// after (force inline base64 to avoid redirects entirely)\nimageRequest.ResponseFormat = openai.CreateImageResponseFormatB64JSON","handlingStrategy":"fallback","validationCode":"// No client-side pre-check; the strategic move is to request b64_json so no fetch happens\nif strings.HasPrefix(strings.ToLower(adapter.model), \"dall-e\") {\n    imageRequest.ResponseFormat = openai.CreateImageResponseFormatB64JSON\n}","typeGuard":null,"tryCatchPattern":"data, err := downloadGeneratedImage(ctx, result.URL)\nif err != nil && strings.Contains(err.Error(), \"redirect is not allowed\") {\n    // switch to inline delivery to bypass redirect handling entirely\n    imageRequest.ResponseFormat = openai.CreateImageResponseFormatB64JSON\n    response, rerr := adapter.client.CreateImage(ctx, imageRequest)\n    // ...handle rerr and continue with b64 path\n}\nif err != nil { return err }","preventionTips":["Prefer b64_json for providers whose CDN redirect chains you cannot control.","Flatten redirect chains on any image host you operate; keep every hop https.","Treat multi-hop redirects from a model URL as suspicious."],"tags":["openai","image-generation","security","redirect","ssrf","go"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}