Skip to content

Claude Code → Codex forward leaks user; optional-control 400 stops combo failover #4527

Description

@RHODIZSECURITY

Client or integration

Claude Code (Anthropic Messages ingress), with a failover combo.

Provider or upstream service

Canonical ChatGPT Codex forward destination (openai-responses, authMode: forward). Observed model: gpt-5.3-codex-spark. The user sanitation defect is destination-scoped, not a request to restore any retired model-specific exception.

OpenCodex version

Observed in installed @bitkyc08/opencodex 2.52.0 on Linux on 2026-09-13. The same missing sanitation and terminal classification were reproduced against current dev base df7dc1b (package development version 2.54.0) with synthetic regression tests.

Endpoint or capability

POST /v1/messages → Responses translation → canonical Codex forward wire; pre-output combo failover after an HTTP 400 optional-control incompatibility.

Current behaviour

  1. src/claude/inbound.ts maps metadata.user_id to top-level Responses user and separately derives prompt_cache_key.
  2. src/adapters/openai-responses.ts removes other unsupported native-forward fields, but leaves user on the canonical ChatGPT wire.
  3. Actual ordered attempt telemetry showed Anthropic returning 429, followed by the native Codex target returning 400 / invalid_request_error / Unsupported parameter: user. The combo ended at that target despite subsequent configured targets.
  4. Separate requests to that native target also returned unsupported_value for reasoning.effort: none, with supported values listed as low/medium/high/xhigh. The combo's generic invalid_request_error stop similarly prevented fallback. A configured default of low does not replace an explicit inbound none.

No credentials, account identifiers, conversation bodies, or private project files are included here.

Expected behaviour

  • Remove top-level user only at the canonical Codex destination. Keep the original translated/replay body, session/cache identity, input roles, tool-schema properties and safety identifiers unchanged. Public/key-auth and noncanonical forward destinations must not inherit this deletion.
  • A bounded, intact error envelope specifically rejecting optional user, or reporting unsupported_value for the reasoning-effort field, may advance to the next eligible unattempted combo target before output commitment.
  • These are request-local incompatibilities, not evidence of an unhealthy account/provider: do not record a target/provider cooldown for them.
  • Do not turn every HTTP 400 into a retry, silently drop reasoning controls, change none to a more expensive level, retry the same attempted target, or replay after output/tool commitment. Cancellation, origin/cyber-policy refusals and ambiguous post-send errors remain terminal.

Minimal redacted request or reproduction

Synthetic Claude-shaped input, with a configured failover combo and a canonical Codex target:

{
  "model": "combo/repro",
  "max_tokens": 32,
  "metadata": { "user_id": "synthetic-claude-session" },
  "messages": [{ "role": "user", "content": "Reply OK." }]
}

For deterministic reproduction, use three synthetic upstream targets: A returns HTTP 429, B (openai-responses) returns either exact 400 envelope below, and C returns a valid completion. Exercise both streaming and non-streaming requests. Before the change, all four scenarios terminate with 400; with the proposed change, each reaches C exactly once and returns 200.

Actual response or error

{"error":{"type":"invalid_request_error","message":"Unsupported parameter: user"}}

The separately observed reasoning rejection:

{"error":{"type":"invalid_request_error","code":"unsupported_value","message":"Unsupported value: 'none' is not supported with the 'gpt-5.3-codex-spark' model. Supported values are: 'low', 'medium', 'high', and 'xhigh'.","param":"reasoning.effort"}}

Upstream documentation

The expected client requirement is concrete: Claude Code supplies metadata.user_id, and this repository's translator forwards it as user. I am not treating the public Responses API schema as a complete specification of the private Codex backend's accepted fields. The rejection above was observed in real attempt telemetry; the proposed destination guard reuses the repository's existing isCanonicalOpenAiForwardProvider authority. Related destination-envelope precedent: #2765 and #2668.

Suggested mapping or implementation notes

An isolated patch and regression tests are prepared for a dev-targeted pull request. The optional-control classifier accepts only the two narrow structured shapes, with a 16,384-character / three-envelope bound and exact OpenCodex wrapper recognition. Conflicting/unknown codes, reflected JSON, malformed/truncated envelopes and unrelated input/tool/safety fields do not acquire hop permission.

Verification so far: 214 focused tests passed, 0 failed, including the complete combo-server E2E file, the canonical-envelope suite, classification/security negatives, and existing reasoning-downgrade coverage. The final regressions were driven red against unchanged base source first: 4 canonical-envelope failures, 4 classification failures, and 4 synthetic E2E failures. Typecheck, privacy scan and structure checks passed. Full-suite/release-readiness verification is still in progress and will be reported with the PR's exact head.

Additional context and attachments

Please consider the reviewed fix for the next appropriate update. This report does not claim a deployed fix, an upstream merge, live-provider E2E of the patch, or a guaranteed release date.

Checks

  • I searched existing provider and compatibility issues.
  • The request and response were redacted.
  • The expected behaviour is based on an upstream specification or a concrete client requirement.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    account-poolOAuth, credentials, Codex pool, quota, failover, plansproviderProvider adapters, OpenAI-compat presets, upstream API quirksprovider-compatibilityProvider compatibility reports

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions