{"record":{"id":"94b7db4b532a1996","repo":"larksuite/cli","slug":"download-proxy-selection-is-missing","errorCode":null,"errorMessage":"download proxy selection is missing","messagePattern":"download proxy selection is missing","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/validate/url.go","lineNumber":360,"sourceCode":"\n\tselectProxy := source.Proxy\n\tdirect := cloneDownloadHTTPTransport(source)\n\tdirect.Proxy = nil\n\tconfigureDirectDownloadTransport(direct)\n\tif selectProxy == nil {\n\t\treturn direct, true\n\t}\n\n\t// The proxied branch validates the requested URL before construction and\n\t// on every redirect. Its TCP peer is the selected proxy, so applying the\n\t// direct-origin IP guard there would incorrectly reject trusted loopback or\n\t// private-network proxies. Freeze the selected proxy in request context so\n\t// a stateful selector cannot switch the second lookup to direct egress.\n\tproxied := cloneDownloadHTTPTransport(source)\n\tproxied.Proxy = func(req *http.Request) (*url.URL, error) {\n\t\tselected, ok := req.Context().Value(selectedDownloadProxyKey{}).(*url.URL)\n\t\tif !ok || selected == nil {\n\t\t\treturn nil, fmt.Errorf(\"download proxy selection is missing\")\n\t\t}\n\t\tcloned := *selected\n\t\treturn &cloned, nil\n\t}\n\treturn &proxyAwareDownloadTransport{\n\t\tselectProxy:        selectProxy,\n\t\tdirect:             direct,\n\t\tproxied:            proxied,\n\t\tlookupIP:           lookupIP,\n\t\tproxiedByTLSServer: make(map[string]*http.Transport),\n\t}, true\n}\n\nfunc cloneDownloadHTTPTransport(source *http.Transport) *http.Transport {\n\tcloned := source.Clone()\n\tif cloned.TLSNextProto == nil {\n\t\tif _, ok := source.TLSNextProto[\"h2\"]; ok {\n\t\t\tcloned.ForceAttemptHTTP2 = true","sourceCodeStart":342,"sourceCodeEnd":378,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/validate/url.go#L342-L378","documentation":"This error is thrown by the download transport's Proxy callback when the request context does not carry a pre-selected proxy URL under selectedDownloadProxyKey{}. The download path first calls selectProxy once and freezes the result into the request context so a stateful selector cannot flip the second dial to direct egress; the per-request Proxy function then only reads that frozen value. If the key is absent or holds a non-*url.URL value, the transport refuses to proceed rather than silently going direct.","triggerScenarios":"An http.Request is sent through proxyAwareDownloadTransport's proxied transport without the context value set by the select-then-freeze step (e.g. the request bypassed cloneDownloadHTTPTransport's selection phase, the context was replaced/derived with a new background, or the selection step stored a wrong type).","commonSituations":"Reusing an http.Client/transport built for the proxied download path with requests created elsewhere; wrapping or re-writing the request context in middleware; tests constructing requests by hand against the pinned transport; a selector returning a value stored under a different context key after refactoring.","solutions":["Ensure the request passed through the normal download flow that calls selectProxy and stores the result in the context under selectedDownloadProxyKey{} before the transport dials","Do not replace or strip the request Context (context.Background()/TODO) between selection and dialing","Verify the value stored in the context is *url.URL, not a string or wrapper type","If constructing requests in tests, set the context value explicitly: req = req.WithContext(context.WithValue(ctx, selectedDownloadProxyKey{}, proxyURL))"],"exampleFix":"// before\nreq, _ := http.NewRequestWithContext(context.Background(), \"GET\", url, nil)\nresp, err := proxiedClient.Do(req) // selection lost\n// after\nctx = context.WithValue(ctx, selectedDownloadProxyKey{}, selectedProxy)\nreq, _ = http.NewRequestWithContext(ctx, \"GET\", url, nil)\nresp, err := proxiedClient.Do(req)","handlingStrategy":"validation","validationCode":"func hasSelectedDownloadProxy(ctx context.Context) bool {\n    sel, ok := ctx.Value(selectedDownloadProxyKey{}).(*url.URL)\n    return ok && sel != nil\n}\n// before sending: if !hasSelectedDownloadProxy(req.Context()) { re-run the select-and-freeze step }","typeGuard":"sel, ok := req.Context().Value(selectedDownloadProxyKey{}).(*url.URL)\nif !ok || sel == nil { /* selection missing — restore before dialing */ }","tryCatchPattern":"if err := runDownload(ctx, req); err != nil && strings.Contains(err.Error(), \"download proxy selection is missing\") {\n    // rebuild the request via the normal download entrypoint so selection runs again\n}","preventionTips":["Always start downloads from the library's download entrypoint, not by hand-rolling requests against the pinned transport","Never overwrite a request's Context after proxy selection","Keep the context key and value type (*url.URL) unchanged when refactoring","Add a test asserting requests carry the frozen proxy value"],"tags":["http","proxy","ssrf","context"],"backgroundTag":"download-proxy-context-missing","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}