{"record":{"id":"d8d9f9f263adfb8e","repo":"chenhg5/cc-connect","slug":"too-many-redirects","errorCode":null,"errorMessage":"too many redirects","messagePattern":"too many redirects","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"platform/feishu/feishu.go","lineNumber":6596,"sourceCode":"\t}\n\treturn u.Hostname()\n}\n\nfunc fetchRichCardRemoteImage(ctx context.Context, rawURL string) ([]byte, string, error) {\n\tu, err := url.Parse(rawURL)\n\tif err != nil || (u.Scheme != \"http\" && u.Scheme != \"https\") || u.Host == \"\" {\n\t\treturn nil, \"\", errors.New(\"invalid remote image URL\")\n\t}\n\n\tclient := &http.Client{\n\t\tTimeout: richCardImageFinalWait,\n\t\tTransport: &http.Transport{\n\t\t\tDialContext:           dialPublicRichCardImageContext,\n\t\t\tResponseHeaderTimeout: richCardImageFinalWait,\n\t\t},\n\t\tCheckRedirect: func(req *http.Request, via []*http.Request) error {\n\t\t\tif len(via) >= 3 {\n\t\t\t\treturn errors.New(\"too many redirects\")\n\t\t\t}\n\t\t\tif !isRemoteRichCardImageURL(req.URL.String()) {\n\t\t\t\treturn errors.New(\"redirected to unsupported image URL\")\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\treq.Header.Set(\"User-Agent\", \"cc-connect-feishu-rich-card-image-resolver/1.0\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}","sourceCodeStart":6578,"sourceCodeEnd":6614,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/feishu/feishu.go#L6578-L6614","documentation":"The rich-card image fetcher limits redirect chains to 3 hops via http.Client's CheckRedirect. When a URL redirects more than twice, it returns this error to stop infinite/long redirect loops, which also serves as SSRF protection since each hop is re-validated. The http.Client then surfaces it wrapped in url.Error.","triggerScenarios":"Fetching a rich card image whose server issues a >=3-deep redirect chain (platform/feishu/feishu.go:6596); also triggered when any redirect hop fails the isRemoteRichCardImageURL re-validation, though that yields the redirect-target error instead.","commonSituations":"Image CDN behind multiple chained redirects (auth -> geo -> CDN); redirect loops caused by misconfigured servers; shortener services that redirect several times; expired URLs redirecting to login pages repeatedly.","solutions":["Use a direct, non-redirecting image URL (the final destination after following redirects manually).","Pre-resolve the redirect chain once and store the final URL.","Ask the content provider to fix the redirect loop; verify the URL with curl -IL to count hops."],"exampleFix":"// before\ncard.ImageURL = \"https://shortener.example.com/img/abc123\"\n// after\nfinalURL := resolveRedirects(\"https://shortener.example.com/img/abc123\") // follow once at ingestion time\ncard.ImageURL = finalURL","handlingStrategy":"fallback","validationCode":"client := &http.Client{CheckRedirect: func(req *http.Request, via []*http.Request) error {\n    if len(via) >= 3 { return errors.New(\"too many redirects\") }\n    return nil\n}}\n// HEAD the URL first to count hops before card rendering","typeGuard":null,"tryCatchPattern":"var urlErr *url.Error\nif errors.As(err, &urlErr) && strings.Contains(urlErr.Err.Error(), \"too many redirects\") {\n    // use a cached/proxied copy of the image instead\n}","preventionTips":["Prefer direct CDN URLs over shorteners in generated card content.","Resolve shortener chains once at ingestion and store the final URL.","Cache successfully fetched images and reuse them instead of refetching redirecting URLs."],"tags":["feishu","http","redirects","image","ssrf"],"backgroundTag":"too-many-redirects","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}