siyuan-note/siyuan · error

generated image redirect is not allowed

Error message

generated image redirect is not allowed

What it means

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.

Source

Thrown at kernel/util/openai.go:822

	data, err := io.ReadAll(io.LimitReader(resp.Body, maxGeneratedImageBytes+1))
	if err != nil {
		return nil, err
	}
	if len(data) > maxGeneratedImageBytes {
		return nil, errors.New("generated image exceeds size limit")
	}
	return data, nil
}

func generatedImageHTTPClient() *http.Client {
	return &http.Client{
		Transport: &http.Transport{
			Proxy:       http.ProxyFromEnvironment,
			DialContext: generatedImageDialer().DialContext,
		},
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			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")
			}

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Switch the request to b64_json so no URL fetch / redirect occurs.
  2. If you operate the CDN, flatten the redirect chain and keep every hop on https.
  3. Verify the returned URL is the final asset URL, not a short-link that bounces.

Example fix

// before
// request relies on URL delivery; provider returns multi-hop redirects
imageRequest.ResponseFormat = ""  // provider picks URL

// after (force inline base64 to avoid redirects entirely)
imageRequest.ResponseFormat = openai.CreateImageResponseFormatB64JSON
Defensive patterns

Strategy: fallback

Validate before calling

// No client-side pre-check; the strategic move is to request b64_json so no fetch happens
if strings.HasPrefix(strings.ToLower(adapter.model), "dall-e") {
    imageRequest.ResponseFormat = openai.CreateImageResponseFormatB64JSON
}

Try / catch

data, err := downloadGeneratedImage(ctx, result.URL)
if err != nil && strings.Contains(err.Error(), "redirect is not allowed") {
    // switch to inline delivery to bypass redirect handling entirely
    imageRequest.ResponseFormat = openai.CreateImageResponseFormatB64JSON
    response, rerr := adapter.client.CreateImage(ctx, imageRequest)
    // ...handle rerr and continue with b64 path
}
if err != nil { return err }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/7ac31172a33e3132. Report an issue: GitHub.