{"record":{"id":"43ab3ea22a4436c8","repo":"siyuan-note/siyuan","slug":"stopped-after-10-redirects","errorCode":null,"errorMessage":"stopped after 10 redirects","messagePattern":"stopped after 10 redirects","errorType":"http","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/plugin/server.go","lineNumber":237,"sourceCode":"\t\tif !shouldProxyHeader(key, connectionHeaders) {\n\t\t\tcontinue\n\t\t}\n\t\tdst.Del(key)\n\t\tfor _, value := range values {\n\t\t\tdst.Add(key, value)\n\t\t}\n\t}\n}\n\nfunc newProxyHTTPClient() *http.Client {\n\treturn &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tDialContext:        util.SSRFSafeDialer(30 * time.Second).DialContext,\n\t\t\tDisableCompression: true,\n\t\t},\n\t\tCheckRedirect: func(req *http.Request, via []*http.Request) error {\n\t\t\tif len(via) >= 10 {\n\t\t\t\treturn fmt.Errorf(\"stopped after 10 redirects\")\n\t\t\t}\n\t\t\treq.Header.Del(\"Referer\")\n\t\t\treturn nil\n\t\t},\n\t\tTimeout: 0,\n\t}\n}\n\nfunc writeProxyResponse(c *gin.Context, proxy *ResponseProxy) {\n\tif proxy.URL == \"\" {\n\t\tc.String(http.StatusBadRequest, \"missing proxy url\")\n\t\treturn\n\t}\n\ttargetURL, err := url.ParseRequestURI(proxy.URL)\n\tif err != nil {\n\t\tc.String(http.StatusBadRequest, \"parse proxy url failed: %s\", err.Error())\n\t\treturn\n\t}","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/plugin/server.go#L219-L255","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Resolve the final URL server-side and configure the plugin to target it directly (fewer hops).","Check the target host for a redirect loop (auth, http->https, www<->apex).","If the chain is legitimate but long, pre-resolve redirects once and cache the final URL rather than re-chaining per request."],"exampleFix":"// before\nproxy.URL = \"http://example.com/file\" // 12-hop redirect chain\n// after\nproxy.URL = \"https://cdn.example.com/path/file\" // final resolved URL","handlingStrategy":"retry","validationCode":"// Caller side: pre-resolve the final URL to minimize redirect hops.\nasync function resolveFinalURL(u: string): Promise<string> {\n  const r = await fetch(u, { method: 'HEAD', redirect: 'follow' })\n  return r.url\n}","typeGuard":null,"tryCatchPattern":"try { await proxyFetch(url) }\ncatch (e) {\n  if (/stopped after 10 redirects/i.test(String(e))) {\n    const final = await resolveFinalURL(url)\n    return proxyFetch(final)\n  }\n  throw e\n}","preventionTips":["Configure plugins with the final resolved URL, not a redirecting alias.","Watch for auth/CDN redirect loops on the target host.","Cache the resolved final URL instead of re-chaining per request."],"tags":["plugin","http-proxy","redirect","network","ssrf-safe"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}