siyuan-note/siyuan · warning

stopped after 10 redirects

Error message

stopped after 10 redirects

What it means

Returned by the plugin proxy HTTP client's CheckRedirect when an outbound proxied request exceeded 10 redirects (len(via) >= 10). This is the standard net/http redirect-loop guard, applied to the plugin server's proxy egress which uses SSRFSafeDialer. It prevents infinite redirect chains / open-redirect abuse.

Source

Thrown at kernel/plugin/server.go:237

		if !shouldProxyHeader(key, connectionHeaders) {
			continue
		}
		dst.Del(key)
		for _, value := range values {
			dst.Add(key, value)
		}
	}
}

func newProxyHTTPClient() *http.Client {
	return &http.Client{
		Transport: &http.Transport{
			DialContext:        util.SSRFSafeDialer(30 * time.Second).DialContext,
			DisableCompression: true,
		},
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			if len(via) >= 10 {
				return fmt.Errorf("stopped after 10 redirects")
			}
			req.Header.Del("Referer")
			return nil
		},
		Timeout: 0,
	}
}

func writeProxyResponse(c *gin.Context, proxy *ResponseProxy) {
	if proxy.URL == "" {
		c.String(http.StatusBadRequest, "missing proxy url")
		return
	}
	targetURL, err := url.ParseRequestURI(proxy.URL)
	if err != nil {
		c.String(http.StatusBadRequest, "parse proxy url failed: %s", err.Error())
		return
	}

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Resolve the final URL server-side and configure the plugin to target it directly (fewer hops).
  2. Check the target host for a redirect loop (auth, http->https, www<->apex).
  3. If the chain is legitimate but long, pre-resolve redirects once and cache the final URL rather than re-chaining per request.

Example fix

// before
proxy.URL = "http://example.com/file" // 12-hop redirect chain
// after
proxy.URL = "https://cdn.example.com/path/file" // final resolved URL
Defensive patterns

Strategy: retry

Validate before calling

// Caller side: pre-resolve the final URL to minimize redirect hops.
async function resolveFinalURL(u: string): Promise<string> {
  const r = await fetch(u, { method: 'HEAD', redirect: 'follow' })
  return r.url
}

Try / catch

try { await proxyFetch(url) }
catch (e) {
  if (/stopped after 10 redirects/i.test(String(e))) {
    const final = await resolveFinalURL(url)
    return proxyFetch(final)
  }
  throw e
}

Prevention

When it happens

Trigger: A plugin's proxy handler (writeProxyResponse) fetches a URL whose server responds with a redirect chain longer than 10 hops; or a self-referential loop between hosts.

Common situations: Target URL moved and chains through many hops; CDN/auth gateway redirect loop; misconfigured target returning Location to itself; plugin proxies an URL that requires login and bounces through auth redirects.

Related errors


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