From 91561df0b16b5646e6478fc8bb5af687270b4273 Mon Sep 17 00:00:00 2001 From: sususu Date: Sat, 1 Aug 2026 13:01:33 +0800 Subject: [PATCH] fix(codex): align websocket cloaking headers --- config.example.yaml | 2 +- internal/config/config_types.go | 2 +- .../executor/codex_executor_request.go | 11 +++- .../codex_websockets_executor_test.go | 64 ++++++++++++++++++- .../executor/codex_websockets_request.go | 1 + 5 files changed, 73 insertions(+), 7 deletions(-) diff --git a/config.example.yaml b/config.example.yaml index 2a8815958..de4ff9c0f 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -220,7 +220,7 @@ codex: # Some superstitious users believe request tracking identifiers can be used # as evidence for TOS enforcement bans; this option only satisfies those odd concerns. identity-confuse: false - # Disable forcing the official Codex User-Agent and Originator headers on HTTP requests. + # Disable forcing the official Codex User-Agent and Originator headers on HTTP/SSE and WebSocket requests. disable-codex-cloaking: false # When true, optimize Codex Desktop and codex-tui requests for multi-agent v2. # This refreshes Codex spawn_agent model details, removes message parameter encryption, diff --git a/internal/config/config_types.go b/internal/config/config_types.go index df53b904d..e6609e5b7 100644 --- a/internal/config/config_types.go +++ b/internal/config/config_types.go @@ -127,7 +127,7 @@ type XAIConfig struct { // CodexConfig configures provider-wide Codex request behavior. type CodexConfig struct { IdentityConfuse bool `yaml:"identity-confuse" json:"identity-confuse"` - // DisableCodexCloaking disables forcing the official Codex identity headers on HTTP requests. + // DisableCodexCloaking disables forcing the official Codex identity headers on HTTP/SSE and WebSocket requests. DisableCodexCloaking bool `yaml:"disable-codex-cloaking" json:"disable-codex-cloaking"` // OptimizeMultiAgentV2 optimizes official Codex multi-agent requests. OptimizeMultiAgentV2 bool `yaml:"optimize-multi-agent-v2" json:"optimize-multi-agent-v2"` diff --git a/internal/runtime/executor/codex_executor_request.go b/internal/runtime/executor/codex_executor_request.go index 8a76620c1..e079a846c 100644 --- a/internal/runtime/executor/codex_executor_request.go +++ b/internal/runtime/executor/codex_executor_request.go @@ -352,10 +352,15 @@ func applyCodexHeadersFromSources(r *http.Request, auth *cliproxyauth.Auth, toke attrs = auth.Attributes } util.ApplyCustomHeadersFromAttrs(r, attrs) - if cfg != nil && !cfg.Codex.DisableCodexCloaking { - r.Header.Set("User-Agent", codexUserAgent) - r.Header.Set("Originator", codexOriginator) + applyCodexCloakingHeaders(r.Header, cfg) +} + +func applyCodexCloakingHeaders(headers http.Header, cfg *config.Config) { + if headers == nil || cfg == nil || cfg.Codex.DisableCodexCloaking { + return } + headers.Set("User-Agent", codexUserAgent) + headers.Set("Originator", codexOriginator) } func normalizeCodexInstructions(body []byte) []byte { diff --git a/internal/runtime/executor/codex_websockets_executor_test.go b/internal/runtime/executor/codex_websockets_executor_test.go index 3d6b2ade2..9197c45a1 100644 --- a/internal/runtime/executor/codex_websockets_executor_test.go +++ b/internal/runtime/executor/codex_websockets_executor_test.go @@ -997,7 +997,63 @@ func TestApplyCodexWebsocketHeadersDefaultsToCurrentResponsesBeta(t *testing.T) } } -func TestApplyCodexWebsocketHeadersPassesThroughClientIdentityHeaders(t *testing.T) { +func TestApplyCodexWebsocketHeadersDefaultsToCodexCloaking(t *testing.T) { + tests := []struct { + name string + auth *cliproxyauth.Auth + token string + }{ + { + name: "OAuth", + auth: &cliproxyauth.Auth{ + Provider: "codex", + Attributes: map[string]string{ + "header:User-Agent": "custom-ua", + "header:Originator": "custom-origin", + }, + }, + }, + { + name: "API key", + auth: &cliproxyauth.Auth{ + Provider: "codex", + Attributes: map[string]string{ + "api_key": "sk-test", + "header:User-Agent": "custom-ua", + "header:Originator": "custom-origin", + }, + }, + token: "sk-test", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := &config.Config{ + CodexHeaderDefaults: config.CodexHeaderDefaults{UserAgent: "config-ua"}, + } + ctx := contextWithGinHeaders(map[string]string{ + "User-Agent": "client-ua", + "Originator": "client-origin", + }) + headers := http.Header{} + headers.Set("User-Agent", "existing-ua") + headers.Set("Originator", "existing-origin") + + headers = applyCodexWebsocketHeaders(ctx, headers, tt.auth, tt.token, cfg) + + if got := headers.Get("User-Agent"); got != codexUserAgent { + t.Fatalf("User-Agent = %q, want %q", got, codexUserAgent) + } + if got := headers.Get("Originator"); got != codexOriginator { + t.Fatalf("Originator = %q, want %q", got, codexOriginator) + } + }) + } +} + +func TestApplyCodexWebsocketHeadersPassesThroughClientIdentityHeadersWhenCloakingDisabled(t *testing.T) { + cfg := &config.Config{Codex: config.CodexConfig{DisableCodexCloaking: true}} auth := &cliproxyauth.Auth{ Provider: "codex", Metadata: map[string]any{"email": "user@example.com"}, @@ -1011,7 +1067,7 @@ func TestApplyCodexWebsocketHeadersPassesThroughClientIdentityHeaders(t *testing "session-id": "legacy-session", }) - headers := applyCodexWebsocketHeaders(ctx, http.Header{}, auth, "", nil) + headers := applyCodexWebsocketHeaders(ctx, http.Header{}, auth, "", cfg) if got := headers.Get("Originator"); got != "Codex Desktop" { t.Fatalf("Originator = %s, want %s", got, "Codex Desktop") @@ -1059,6 +1115,7 @@ func TestApplyCodexWebsocketHeadersCanonicalizesLegacyUnderscoreSessionHeader(t func TestApplyCodexWebsocketHeadersUsesConfigDefaultsForOAuth(t *testing.T) { cfg := &config.Config{ + Codex: config.CodexConfig{DisableCodexCloaking: true}, CodexHeaderDefaults: config.CodexHeaderDefaults{ UserAgent: "my-codex-client/1.0", BetaFeatures: "feature-a,feature-b", @@ -1084,6 +1141,7 @@ func TestApplyCodexWebsocketHeadersUsesConfigDefaultsForOAuth(t *testing.T) { func TestApplyCodexWebsocketHeadersPrefersExistingHeadersOverClientAndConfig(t *testing.T) { cfg := &config.Config{ + Codex: config.CodexConfig{DisableCodexCloaking: true}, CodexHeaderDefaults: config.CodexHeaderDefaults{ UserAgent: "config-ua", BetaFeatures: "config-beta", @@ -1113,6 +1171,7 @@ func TestApplyCodexWebsocketHeadersPrefersExistingHeadersOverClientAndConfig(t * func TestApplyCodexWebsocketHeadersConfigUserAgentOverridesClientHeader(t *testing.T) { cfg := &config.Config{ + Codex: config.CodexConfig{DisableCodexCloaking: true}, CodexHeaderDefaults: config.CodexHeaderDefaults{ UserAgent: "config-ua", BetaFeatures: "config-beta", @@ -1139,6 +1198,7 @@ func TestApplyCodexWebsocketHeadersConfigUserAgentOverridesClientHeader(t *testi func TestApplyCodexWebsocketHeadersIgnoresConfigForAPIKeyAuth(t *testing.T) { cfg := &config.Config{ + Codex: config.CodexConfig{DisableCodexCloaking: true}, CodexHeaderDefaults: config.CodexHeaderDefaults{ UserAgent: "config-ua", BetaFeatures: "config-beta", diff --git a/internal/runtime/executor/codex_websockets_request.go b/internal/runtime/executor/codex_websockets_request.go index fef258336..7d1cf92dd 100644 --- a/internal/runtime/executor/codex_websockets_request.go +++ b/internal/runtime/executor/codex_websockets_request.go @@ -124,6 +124,7 @@ func applyCodexWebsocketHeaders(ctx context.Context, headers http.Header, auth * attrs = auth.Attributes } util.ApplyCustomHeadersFromAttrs(&http.Request{Header: headers}, attrs) + applyCodexCloakingHeaders(headers, cfg) return headers }