Originator Persona Hardening Job Spec #1
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originator Persona Hardening Job Spec
Goal
Prevent upstream token revocation by making OAuth minting personas and upstream request personas congruent, and by removing downstream client identity fields that can reveal a persona mismatch.
The chosen canonical persona is:
Background
codex-lb proxies Codex/Responses traffic to ChatGPT upstream using stored OAuth credentials. Those credentials are minted through codex-lb's OAuth flow. The current default OAuth authorize URL includes:
However, proxied upstream requests can preserve downstream client identity headers. For example, Codex CLI can send:
OpenCode may not send an explicit
originator, but its traffic can still expose an OpenCode-lookingUser-Agentorx-openai-client-*fields while using OAuth tokens minted under the desktop persona.This creates a suspicious envelope:
Live incident evidence showed usage refresh was only the detector, not the revocation source. Affected accounts had successful usage refreshes shortly before upstream Codex WebSocket traffic, then
401 token_revokedshortly after:Important nuance: OpenCode's external
/v1/responsespath is not necessarily pure upstream HTTP. codex-lb's HTTP responses session bridge opens/reuses an upstream/backend-api/codex/responsesWebSocket internally, so both OpenCode and Codex CLI can exercise the upstream native Codex WebSocket endpoint.Agreed Principles
Extra fields that identify a mismatched downstream client should be omitted or normalized.
Use
codex_chatgpt_desktopas the canonical persona for this repo/default deployment.Do not silently change the persona for existing tokens based on downstream traffic.
If originator configurability remains, it should affect token minting and the matching upstream request persona together. Existing tokens should keep their minted persona if persisted per account.
Target Upstream Persona Envelope
For upstream ChatGPT/Codex requests that use stored account OAuth tokens, send a minimal, coherent envelope. Example:
Do not forward downstream identity fields that contradict the canonical persona, such as:
If a
User-Agentis required upstream, stamp a stable first-party-compatible value that matchescodex_chatgpt_desktop; otherwise omit it.Required Workflow
This is a behavior change. Follow the repo's OpenSpec-first workflow before coding.
openspec/changes/<slug>/.context.mdor change notes, notdocs/.openspec validate --specsand targeted tests.CHANGELOG.md.Suggested change slug:
Relevant Existing Specs
openspec/specs/outbound-http-clients/spec.mdoriginator.codex_chatgpt_desktop.openspec/specs/responses-api-compat/spec.mdopenspec/specs/responses-api-compat/ops.mdRelevant Code
app/core/config/settings.pyoauth_originator: str = "codex_chatgpt_desktop"upstream_stream_transport: "auto"app/core/clients/oauth.pybuild_authorization_url()addsoriginatorto authorize URLs.app/core/auth/refresh.pysettings.oauth_client_id; no originator is currently stamped.app/core/clients/proxy_websocket.pyfilter_inbound_websocket_headers()and_build_upstream_websocket_headers()preserve many downstream headers and add auth/account headers._ensure_responses_websocket_beta_header()addsopenai-beta: responses_websockets=2026-02-06.app/core/clients/proxy.pyfilter_inbound_headers()and_build_upstream_websocket_headers()for direct streaming paths._NATIVE_CODEX_ORIGINATORSincludescodex_chatgpt_desktop,codex_cli_rs, etc.app/modules/proxy/_service/websocket/mixin.py/backend-api/codex/responsesWebSocket proxy path.app/modules/proxy/_service/http_bridge/mixin.py_create_http_bridge_session()calls_open_upstream_websocket_with_budget()with filtered client headers.app/modules/proxy/_service/http_bridge/streaming.py/v1/responsesHTTP path can route through the HTTP bridge.tests/unit/test_oauth_client.pytests/unit/test_proxy_utils.pytests/integration/test_proxy_responses.pytest_proxy_responses_forwards_native_codex_headerscurrently expects native header preservation; this likely needs revision.Implementation Tasks
1. Define Persona Sanitization Policy
Add a central helper for outbound account-authenticated ChatGPT/Codex requests. The helper should:
originator: codex_chatgpt_desktopfor account-authenticated upstream Codex requests.openai-betafor WebSocket support.x-request-id/request-id.chatgpt-account-idonly as injected from the selected account, not downstream.Authorizationonly as injected from the selected account token, not downstream.Candidate helper names:
Keep the implementation small and centralized so HTTP bridge, direct WebSocket, and direct stream paths cannot diverge.
2. Apply Policy to Upstream WebSocket Handshakes
Update upstream WebSocket header construction in both clients:
app/core/clients/proxy_websocket.py::_build_upstream_websocket_headersapp/core/clients/proxy.py::_build_upstream_websocket_headersRequired behavior:
originatorMUST NOT be forwarded.originatorMUST becodex_chatgpt_desktopfor stored account-token requests.user-agentMUST NOT be forwarded unless normalized to the canonical persona.x-openai-client-*identity fields MUST NOT be forwarded unless normalized to the canonical persona.x-codex-*fields that are request semantics, such asx-codex-turn-state, may be preserved if needed for session continuity.x-codex-*fields that identify a client/product should be reviewed and dropped if they conflict.3. Apply Policy to Upstream HTTP/SSE Responses Calls
Audit
app/core/clients/proxy.py::_build_upstream_headers().Even if the immediate incident centers on upstream WebSockets, HTTP/SSE account-token calls should not expose a contradictory downstream persona either.
Required behavior:
4. Consider Per-Account Originator Persistence
Preferred robust design:
oauth_originatoror equivalent account metadata persisted at OAuth mint/import time.build_authorization_url().codex_chatgpt_desktop.Minimal acceptable design for this change if avoiding migration scope:
settings.oauth_originator, currentlycodex_chatgpt_desktop, consistently for both OAuth minting and upstream request stamping.CODEX_LB_OAUTH_ORIGINATORrequires reauthenticating accounts minted under the old persona.The preferred design is safer long-term, but the minimal design may be acceptable if the project wants a smaller fix. If choosing minimal, include clear OpenSpec/context notes about the operational constraint.
5. Revisit Existing Tests That Require Header Preservation
Some existing tests assert forwarding native Codex headers exactly. Those expectations are now unsafe.
Update them to assert:
For example, a test that sends:
Should assert upstream sees:
And still preserves required continuity headers if applicable:
6. Add Regression Coverage
Add targeted unit/integration tests for:
originator=codex_chatgpt_desktop.originatortocodex_chatgpt_desktop.User-Agentorx-openai-client-*identity fields.Suggested existing test files to extend:
tests/unit/test_proxy_utils.pytests/unit/test_oauth_client.pytests/integration/test_proxy_responses.pyAcceptance Criteria
Implementation is ready when:
codex_chatgpt_desktopby default.originatorvalues.originator=codex_chatgpt_desktopor the account's persisted equivalent.opencode,codex_cli_rs, or other non-canonical personas are omitted or normalized.openspec validate --specspasses.Non-Goals
reauth_requiredin this change unless it is already scoped into the chosen OpenSpec change. That is a separate containment bug.codex_chatgpt_desktop.Operational Notes
After this fix, operators should not mix tokens minted with one persona and outbound traffic stamped with another persona.
If per-account originator persistence is not implemented, changing
CODEX_LB_OAUTH_ORIGINATORshould be treated as requiring account reauthentication. Otherwise old accounts may have tokens minted under the previous persona while new outbound traffic uses the new configured persona.For debugging future incidents, enable upstream summary logging or conversation archive only in a controlled environment because payloads and headers may contain sensitive data.
Suggested Verification Commands
Adjust test selection based on actual changes, but likely commands are:
bug: <one-line summary of what's broken>to Originator Persona Hardening Job Specbug: <one-line summary of what's broken>to Originator Persona Hardening Job SpecScope update
This hardening should apply to all account-token upstream calls, not only Codex/Responses stream and websocket paths.
Current-code review against
HEADshows the original issue is still valid, with these amendments:app/core/clients/proxy.py::_build_upstream_headers,app/core/clients/proxy.py::_build_upstream_websocket_headers,app/core/clients/proxy_websocket.py::_build_upstream_websocket_headers, andapp/core/clients/proxy.py::_build_upstream_transcribe_headers.originator,user-agent,x-openai-client-*, or other product/client identity fields unless explicitly normalized to the account-token persona.originatorfrom the account minted persona when persisted; otherwise use configuredsettings.oauth_originatorwith the existing defaultcodex_chatgpt_desktop.session_idor requiredx-codex-*turn-state fields.Current notable references:
app/core/clients/proxy.py::_build_upstream_headers()also feeds compact, thread-goal, and Codex control.app/core/clients/proxy.py::_build_upstream_transcribe_headers()currently forwardsuser-agent,x-openai-*, andx-codex-*, so it needs explicit review under the all account-token policy.app/db/models.py::Accountstill has no persisted originator field.This supersedes the narrower wording in the original issue where it only names Codex/Responses paths.