{"record":{"id":"89206ce5d22e9f48","repo":"siyuan-note/siyuan","slug":"generated-image-url-must-use-https","errorCode":null,"errorMessage":"generated image URL must use HTTPS","messagePattern":"generated image URL must use HTTPS","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/openai.go","lineNumber":930,"sourceCode":"\t} else if result.URL != \"\" {\n\t\tdata, err = downloadGeneratedImage(requestCtx, result.URL)\n\t} else {\n\t\terr = errors.New(\"image model returned neither base64 data nor URL\")\n\t}\n\tif err != nil {\n\t\treturn GeneratedImage{}, err\n\t}\n\tmimeType, extension, err := ValidateGeneratedImage(data)\n\tif err != nil {\n\t\treturn GeneratedImage{}, err\n\t}\n\treturn GeneratedImage{Data: data, MIMEType: mimeType, Extension: extension, RevisedPrompt: result.RevisedPrompt}, nil\n}\n\nfunc downloadGeneratedImage(ctx context.Context, rawURL string) ([]byte, error) {\n\tparsed, err := url.Parse(rawURL)\n\tif err != nil || parsed.Scheme != \"https\" || parsed.Host == \"\" {\n\t\treturn nil, errors.New(\"generated image URL must use HTTPS\")\n\t}\n\tif err = CheckHostSSRF(parsed.Hostname()); err != nil {\n\t\treturn nil, err\n\t}\n\tclient := generatedImageHTTPClient()\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode < 200 || resp.StatusCode >= 300 {\n\t\treturn nil, fmt.Errorf(\"download generated image failed with status %d\", resp.StatusCode)\n\t}\n\tif resp.ContentLength > maxGeneratedImageBytes {","sourceCodeStart":912,"sourceCodeEnd":948,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/openai.go#L912-L948","documentation":"downloadGeneratedImage parses the URL returned by the image model and requires scheme https with a non-empty host. This is an SSRF/security guard: plain-http or malformed URLs returned by a compromised or misbehaving provider are rejected before any request is made. Non-https URLs would also leak generated content in cleartext.","triggerScenarios":"result.URL from the provider starts with http:// (not https), is scheme-relative like //host/img, or fails url.Parse entirely (spaces, control chars, relative path).","commonSituations":"Self-hosted/proxied image service returning http:// LAN URLs; provider returning a relative path instead of an absolute URL; corrupted URL containing unencoded characters; man-in-the-middle or malicious provider response.","solutions":["Configure the provider/proxy to return absolute https:// URLs","If you control the image host, serve it over HTTPS","URL-encode/fix the URL at the provider side; verify with url.Parse what is malformed","As an operator-only workaround behind a trusted private network, host-rewrite the URL to https before passing it downstream (not recommended)"],"exampleFix":"// before\nrawURL := \"http://cdn.example.com/img.png\" // rejected: not https\n// after\nrawURL := \"https://cdn.example.com/img.png\" // pass\ndata, err := downloadGeneratedImage(ctx, rawURL)","handlingStrategy":"validation","validationCode":"u, err := url.Parse(rawURL)\nif err != nil || u.Scheme != \"https\" || u.Host == \"\" {\n    return errors.New(\"image URL must be absolute https\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only integrate image providers that return absolute https URLs","Validate URLs at the boundary before handing them to the downloader","Reject scheme-relative or relative URLs early"],"tags":["security","ssrf","https","url"],"backgroundTag":"invalid-url-format","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"}