From 91ab76740dd1f9c3ec6bffed5dec0387757e4ff4 Mon Sep 17 00:00:00 2001 From: li Date: Tue, 7 Jul 2026 14:51:21 +0800 Subject: [PATCH] fix(executor): stop forwarding downstream User-Agent on wrapped Codex image requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wrapped image path (executeOpenAIImage / executeOpenAIImageStream) called applyCodexHeaders which forwards the downstream client's User-Agent to upstream chatgpt.com. Script UAs like "Python-urllib/3.9" trigger Cloudflare 1010 Access Denied on the /images/generations endpoint. Switch both call sites to applyCodexDirectImageHeaders, which already exists for the direct image path — it clones gin headers and strips User-Agent so the resolution falls through to the safe codexUserAgent default. Closes #3980 --- .../runtime/executor/codex_openai_images.go | 4 +-- .../executor/codex_openai_images_test.go | 32 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/internal/runtime/executor/codex_openai_images.go b/internal/runtime/executor/codex_openai_images.go index 5398179968d..d40de86d9ad 100644 --- a/internal/runtime/executor/codex_openai_images.go +++ b/internal/runtime/executor/codex_openai_images.go @@ -112,7 +112,7 @@ func (e *CodexExecutor) executeOpenAIImage(ctx context.Context, auth *cliproxyau if errCache != nil { return resp, errCache } - applyCodexHeaders(httpReq, auth, apiKey, true, e.cfg) + applyCodexDirectImageHeaders(httpReq, auth, apiKey, true, e.cfg) applyCodexIdentityConfuseHeaders(httpReq.Header, &identityState) recordCodexOpenAIImageRequest(ctx, e.cfg, e.Identifier(), auth, url, httpReq.Header.Clone(), body) @@ -208,7 +208,7 @@ func (e *CodexExecutor) executeOpenAIImageStream(ctx context.Context, auth *clip if errCache != nil { return nil, errCache } - applyCodexHeaders(httpReq, auth, apiKey, true, e.cfg) + applyCodexDirectImageHeaders(httpReq, auth, apiKey, true, e.cfg) applyCodexIdentityConfuseHeaders(httpReq.Header, &identityState) recordCodexOpenAIImageRequest(ctx, e.cfg, e.Identifier(), auth, url, httpReq.Header.Clone(), body) diff --git a/internal/runtime/executor/codex_openai_images_test.go b/internal/runtime/executor/codex_openai_images_test.go index 6bc5b63890d..eb4707067e6 100644 --- a/internal/runtime/executor/codex_openai_images_test.go +++ b/internal/runtime/executor/codex_openai_images_test.go @@ -315,3 +315,35 @@ func TestCodexExecutorDirectOpenAIImageEditUsesImagesEditEndpointForMultipart(t t.Fatalf("mask.image_url = %q, want mask-data data URL; body=%s", maskURL, string(gotBody)) } } + +func TestCodexExecutorWrappedOpenAIImageDoesNotForwardDownstreamUA(t *testing.T) { + var gotUA string + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotUA = r.Header.Get("User-Agent") + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"id":"resp_1","output":[{"type":"image_generation_call","result":"AA=="}],"usage":{"input_tokens":1,"output_tokens":1}}`)) + })) + defer server.Close() + + ctx := contextWithGinHeaders(map[string]string{ + "User-Agent": "Python-urllib/3.9", + }) + executor := NewCodexExecutor(&config.Config{}) + // Use a model name that is NOT gpt-image-1.5 / gpt-image-2 so the request + // takes the wrapped (/responses) path instead of the direct (/images/*) path. + _, _ = executor.Execute(ctx, newCodexOpenAIImageTestAuth(server.URL), cliproxyexecutor.Request{ + Model: "codex/dall-e-3", + Payload: []byte(`{"model":"codex/dall-e-3","prompt":"A cute otter","stream":false}`), + }, codexOpenAIImageTestOptions(codexImagesGenerationsPath, false)) + + if gotUA == "" { + t.Fatal("upstream request was never made (User-Agent not captured)") + } + if gotUA == "Python-urllib/3.9" { + t.Fatalf("downstream User-Agent leaked to upstream: %q", gotUA) + } + if gotUA != codexUserAgent { + t.Fatalf("User-Agent = %q, want codex default %q", gotUA, codexUserAgent) + } +}