20 Commits
Author SHA1 Message Date
cooper-oai 9d2b60303e Back off automatic remote control reconnects with jitter (#50348)
## Why

Closed WebSocket connections previously triggered immediate reconnects, and successful handshakes reset backoff even when connections were short-lived.

## What changed

- Delay automatic reconnects before token refresh or enrollment, and report `Connecting` while waiting.
- Use exponential backoff with jitter between half and all of the delay, starting at 5 seconds and capped at 30 seconds.
- Retain backoff across short-lived connections and reset it after a connection lasts at least 60 seconds.
- Interrupt the reconnect delay on shutdown, auth owner invalidation, or enable/disable changes, including an immediate disable/enable pair.

## Testing

Add connection tests for delayed token refresh, backoff retention and reset, explicit re-enabling, and prompt shutdown. Update retry tests to check jitter bounds, saturation during sustained failures, and backoff retention after a successful handshake.

GitOrigin-RevId: 0b9055ff64ff7d96f2d974b73ced8d4ffd02ac9e
2026-10-02 16:05:51 +00:00
cooper-oai 7219fd735b Use server authentication for explicit remote session commands (#49624)
## Why

Session commands with `--remote` should use the selected server's authentication and execution environment, even when the caller has workload identity configured and cannot read its identity token.

## What changed

Connect `queue`, `archive`, `unarchive`, and `delete` directly to the explicit remote app server after validating configuration. Skip caller credential and runtime initialization, use remote thread parameters, and preserve the caller's working-directory override.

## Testing

Add end-to-end coverage with an unreadable caller identity token to verify queued turns use the server's API key and remote archive, unarchive, and deletion operate on the selected server. Add configuration-validation coverage for all four commands and `--no-daemon` rejection coverage for remote unarchive and delete.

GitOrigin-RevId: 5da7abbb40506cd754a8863e1a9d8fad9b1f09a9
2026-09-30 13:16:08 +00:00
cooper-oai d8f69ea8bc Preserve bootstrap discovery across authentication changes (#49432)
## Why

Embedded app-server configuration discovery must remain available after login and workspace changes, while content access granted to the previous account must be revoked.

## What changed

Give `AuthRouteConfig` a separate application network policy and bind embedded bootstrap authentication to the effective application policy. Use that policy for account-owned content clients and invalidate it when the authenticated owner changes, preserving the local, endpoint-restricted bootstrap policy.

## Testing

Add a regression test covering bootstrap discovery across login, workspace changes, and sign-out; revocation of previous account clients and permits; and continued enforcement of bootstrap endpoint restrictions and local requirements.

GitOrigin-RevId: 083933e69508224f8232349ba3750c02329af06a
2026-09-30 01:44:14 +00:00
cooper-oai 434efa95e6 Honor shared Retry-After deadlines for remote control (#44311)
## Why

Remote-control requests could bypass a server's `Retry-After` deadline through pairing, auth changes, or reconnects. Proactive token refresh could also continue using a valid token despite an explicit server delay.

## What changed

- Share retry deadlines across enrollment, token refresh, pairing, pairing status, and WebSocket handshakes, preserving them across enrollment replacement and disable/enable transitions.
- Honor `Retry-After` on HTTP `429` and `503` pairing and handshake responses, including incomplete pairing response bodies.
- Add up to 30 seconds of jitter once per response without shortening the server's deadline, including for `Retry-After: 0`.
- Defer new requests while retaining valid tokens, and prevent auth changes from shortening the server delay while keeping shutdown responsive.

## Testing

Add regression coverage for shared deadlines, concurrent pairing and handshake failures, auth recovery, disable/enable transitions, retries after expiry, prompt shutdown, and jitter bounds.

GitOrigin-RevId: 69a5a46559031709a4558190c3142eb65e2ae634
2026-09-09 21:50:16 +00:00
cooper-oai b537d5a097 Enforce workspace restrictions for header authentication (#39322)
## What changed

- Validate externally supplied header credentials against configured ChatGPT workspace restrictions using their `chatgpt-account-id` header.
- Reject header credentials with a missing or disallowed account ID, including credentials returned during refresh, without replacing the previously cached authentication.
- Document that identity headers must be bound to validated credentials because Codex uses them for managed authentication policy.

## Testing

- Add coverage for allowed, disallowed, and missing account IDs.
- Add coverage for rejecting a refresh that changes to a disallowed workspace.

GitOrigin-RevId: 3e3db2607cd8f9f1b8000472342e42e4b1cf00b2
2026-08-19 00:28:52 +00:00
cooper-oai 12933b6955 Forward workload identity context during token exchange (#38767)
## What changed

- Read optional workload identity context from `OPENAI_WORKLOAD_IDENTITY_CONTEXT` and forward it unchanged as the `workload_identity_context` token exchange field.
- Treat the context as sensitive by redacting it from session configuration debug output and removing it from model-reachable child environments.
- Include the context in workload identity session fingerprints so sessions with different values cannot share an exchange.

## Testing

- Cover request forwarding, debug redaction, session compatibility, and child-environment scrubbing.

GitOrigin-RevId: fb50700478cf54d9d604944a4ed3e77acc928a0f
2026-08-15 14:31:04 +00:00
cooper-oai fb5aa093e0 Support workload identity in remote exec-server auth (#38610)
## Why

Remote exec-server registry requests need to refresh managed credentials before sending a request. Static auth-header resolution cannot perform the asynchronous token exchange required by workload identity.

## What changed

- Add asynchronous auth-header resolution to `AuthProvider`, with the existing static-header behavior as the default.
- Resolve fresh managed credentials for each remote environment registry request while preserving the expected account and workspace identity.
- Load the cloud configuration bundle during remote exec-server startup when workload identity is selected.

## Testing

- Update the managed-auth and environment-registry auth tests to exercise asynchronous header resolution.

GitOrigin-RevId: 5d60f1127467aaacdb5d1a8f3d92278bc4bf2e29
2026-08-14 17:32:37 +00:00
cooper-oai 5ed321ce00 Protect workload identity auth in app-server account RPCs (#38426)
## Why

Workload identity credentials are owned by the app-server host and must not be replaced, removed, or exported through client account operations.

## What changed

- Reject account login and logout RPCs while workload identity is selected.
- Continue reporting the active authentication method from `getAuthStatus`, but omit the host-owned token even when `includeToken` is requested.

GitOrigin-RevId: 750c9d07ed2f4ba7007bbe75f188573e79749647
2026-08-13 20:04:57 +00:00
cooper-oai 990218bbbd Fail closed when workload identity initialization fails (#38424)
## What changed

- Treat workload identity environment markers as an explicit authentication selection, even when another process credential is present.
- Return initialization errors from `AuthManager` and propagate them through commands and services instead of continuing with an unusable authentication state.
- Make `codex login status` validate workload identity, keep the TUI on an embedded app server for local workload identity, and reject workload identity in `codex mcp-server`, where it is unsupported.

## Testing

- Cover workload identity precedence and partial configuration errors.
- Verify login status reports an unreadable identity assertion and app-server routing enforces the supported workload identity topology.

GitOrigin-RevId: efc6b6b4cd4d61652617de82aaa3d7ffc75d6618
2026-08-13 19:35:22 +00:00
cooper-oai 18dcc7646f Unify external authentication provider handling (#38258)
## What changed

- Use each `ExternalAuth` provider's error classification for resolve, refresh,
  and validation failures.
- Allow runtime providers to be replaced, clearing any recorded permanent
  refresh failure after the replacement resolves successfully.
- Preserve workload identity as immutable, host-managed authentication while
  sharing external ChatGPT credentials through the process-local store.

## Testing

- Cover provider replacement after a permanent failure, provider-defined error
  classification, workload identity immutability, and refreshed headers after
  an unauthorized response.

GitOrigin-RevId: ec75cf9224dc82d3fd10063606c724e3b06fb1e7
2026-08-12 22:04:27 +00:00
cooper-oai 96c8be200c Integrate workload identity with Codex authentication (#38188)
## What changed

- Select workload identity authentication from `OPENAI_FEDERATION_RULE_ID` and
  `OPENAI_IDENTITY_TOKEN_FILE`, while preserving explicit API key and access
  token precedence.
- Adapt workload identity exchanges to the shared authentication manager and
  reuse a compatible process-scoped session for token resolution and refresh.
- Reject incomplete, conflicting, or unsupported configurations and exchanged
  tokens that change the authenticated subject.
- Classify exchange failures for authentication retry handling and invalidate
  rejected cached tokens without discarding a newer concurrent exchange.

## Testing

- Cover configuration selection and validation, session reuse and conflicts,
  identity-preserving refreshes, concurrent refreshes, and retry classification.

GitOrigin-RevId: 62afb514b4e0a59effb08f464ac929633462a71b
2026-08-12 13:27:57 +00:00
cooper-oai b28aa476f4 Add configuration-backed external authentication (#38054)
## What changed

- Add a host-owned external auth source that runtime account APIs cannot replace,
  clear, or log out, and keep its credentials process-local instead of writing
  them to auth storage.
- Let configured providers classify refresh failures. Preserve cached credentials
  after permanent reload failures while retaining transient behavior for runtime
  providers.
- Compare external ChatGPT token data when detecting auth changes so a refreshed
  access token is applied to a retried request.

## Testing

- Cover configured-auth immutability, process-local storage, permanent reload
  failures, runtime error classification, and refreshed headers after a `401`.

GitOrigin-RevId: c6d70a39325c8ba6ca65ae68e12b55609f00a876
2026-08-11 19:04:07 +00:00
cooper-oai 92cbfb4d24 Use the shared environment scrubber in git-utils (#37843)
Call `scrub_non_inheritable_env_vars` directly from each Git command path and
remove the redundant crate-local forwarding helper.

GitOrigin-RevId: 9e4125dc538ea888626bde3cb3646a90712206a2
2026-08-10 15:50:53 +00:00
cooper-oai 936f5eb3ee Add workload identity token exchange support (#37610)
## What changed

- Add the `codex-workload-identity` crate for exchanging a file-backed JWT assertion and federation rule ID for short-lived ChatGPT credentials.
- Cache valid access tokens, refresh them before expiry or after rejection, and coalesce concurrent exchanges. Continue using a still-valid cached token when a proactive refresh fails transiently.
- Validate assertion files, token endpoints, and exchange responses; honor outbound proxy policy for HTTPS endpoints and redact access tokens from debug output.

## Testing

- Cover request encoding, assertion rotation, caching, concurrent refreshes, transient-failure fallback, configuration validation, and malformed inputs and responses.

GitOrigin-RevId: 5496851683c2dcf6aaad6840053b97f7c0be076e
2026-08-08 17:18:02 +00:00
cooper-oai c4513cb982 Prevent launch context from reaching child processes (#37607)
## Why

Model-reachable child processes should not inherit Codex launch context.

## What changed

- Treat `OPENAI_FEDERATION_RULE_ID` and `OPENAI_IDENTITY_TOKEN_FILE` as non-inheritable environment variables, with case-insensitive matching.
- Remove them after shell environment policy overrides and before spawning commands across execution, MCP, hooks, Git helpers, and remote helper processes.

## Testing

- Cover inherited and explicitly configured variants, including mixed-case names.
- Verify the variables are absent from real child environments and app-server command and process execution.

GitOrigin-RevId: 2535527893985fef0995617f4c5b2462bea7c136
2026-08-08 16:58:26 +00:00
cooper-oai d1aaf789ad [login] revoke existing auth before starting login (#27674)
## Why

`codex login` previously persisted newly issued OAuth credentials and
only then attempted to revoke the superseded refresh token. The old
credential must be revoked before a replacement browser or device-code
flow starts, and successful login must not perform any post-login
revocation attempt.

## What changed

- Revoke and clear existing stored auth before browser or device-code
CLI login begins.
- Remove superseded-token detection and revocation from the shared token
persistence path; successful login now only saves the new credentials.
- Read the raw configured auth store during CLI cleanup so
environment-provided auth cannot mask the stored refresh token.
- Preserve `auto` storage fallback semantics when keyring deletion fails
by clearing the fallback auth file.
- Add a process-level CLI regression test that requires the revoke
request to precede every device-login request and occur exactly once.

If replacement login is canceled or fails, the previous local
credentials have already been cleared. Remote revocation remains best
effort, matching explicit logout behavior.

## Validation

### Process-level before/after reproduction

I compiled the real `codex` CLI from the pre-fix parent (`14df0e8833`)
and from the PR implementation (`25c002f23b`; the login behavior is
unchanged at the current head), then ran the same device-code flow
against a local HTTP mock OAuth authority.

Each run:

1. Used a fresh temporary `CODEX_HOME` configured with
`cli_auth_credentials_store = "file"`.
2. Seeded that temporary home with managed ChatGPT auth containing
`old-access` and `old-refresh` tokens.
3. Pointed `CODEX_REVOKE_TOKEN_URL_OVERRIDE` at the mock `/oauth/revoke`
endpoint.
4. Ran the compiled CLI as:

   ```shell
   CODEX_HOME=<temporary-home> \
     CODEX_REVOKE_TOKEN_URL_OVERRIDE=<mock-issuer>/oauth/revoke \
<compiled-codex> login --device-auth --experimental_issuer <mock-issuer>
   ```

5. Recorded every request received by the mock authority. The mock
marked `new-access` valid when `/oauth/token` issued it and invalidated
it if `/oauth/revoke` arrived afterward, reproducing the observed
session-invalidating failure mode. After login exited, the harness also
verified the persisted refresh token and probed a protected endpoint
with `new-access`.

| Build | Observed request order | CLI/persistence result | `new-access`
probe |
| --- | --- | --- | --- |
| Pre-fix | `usercode → device token → OAuth token →
revoke(old-refresh)` | Exit `0`; `new-refresh` persisted | `401` |
| PR | `revoke(old-refresh) → usercode → device token → OAuth token` |
Exit `0`; `new-refresh` persisted | `200` |

The PR run therefore issued exactly one revocation request, before any
request that initiated the replacement login, and issued no revocation
after token exchange.

### Regression coverage


`codex-rs/cli/tests/login.rs::device_login_revokes_existing_auth_before_requesting_new_tokens`
runs the real first-party `codex` binary against a `wiremock` OAuth
server with an isolated temporary `CODEX_HOME`. It asserts:

- the exact request sequence is `/oauth/revoke`,
`/api/accounts/deviceauth/usercode`, `/api/accounts/deviceauth/token`,
then `/oauth/token`;
- there is exactly one revoke request and its body contains
`old-refresh` with the `refresh_token` hint;
- the completed login persists `new-refresh`.

Local validation:

- `just test -p codex-login` — 130 passed
- `just test -p codex-cli` — 280 passed, including the new process-level
regression test
- `just bazel-lock-check`
2026-06-12 12:38:30 -07:00
cooper-oai b5f1bb75d4 [codex-rs] enforce PAT workspace restrictions (#27450)
## Summary

- validate a hydrated personal access token's workspace against
`forced_chatgpt_workspace_id` before persisting `codex login
--with-access-token`
- apply the same PAT-only check when restricted auth managers load
environment, ephemeral, or persisted credentials
- enforce PAT workspace restrictions in the existing central
login-restriction path
- leave Agent Identity and cloud bootstrap behavior unchanged

## Scope

This is intentionally the small PAT-only change. It does not attempt the
broader auth-manager/bootstrap unification; that needs separate design
work.

## Validation

- `CARGO_INCREMENTAL=0 CARGO_TARGET_DIR=/tmp/codex-pat-target just test
-p codex-login -p codex-cli` (410 passed)
- `CARGO_INCREMENTAL=0 CARGO_TARGET_DIR=/tmp/codex-pat-target just fix
-p codex-login -p codex-cli`
- `just fmt`
- `git diff --check`

Context: https://openai.slack.com/archives/D0AUPLV03RQ/p1781138331548269
2026-06-11 14:17:37 -07:00
cooper-oai df7818c7d1 [codex-rs] support v2 personal access tokens (#25731)
## Summary

- add v2 personal access token support for `codex login
--with-access-token` and `CODEX_ACCESS_TOKEN`
- classify opaque `at-` tokens separately from legacy Agent Identity
JWTs
- hydrate required ChatGPT account metadata through AuthAPI
`/v1/user-auth-credential/whoami`
- use PATs directly as bearer tokens while preserving existing ChatGPT
account surfaces
- expose PAT-backed auth as the explicit `personalAccessToken`
app-server auth mode

## Implementation

PAT auth is intentionally small and stateless. Loading a PAT performs
one AuthAPI metadata request, stores the hydrated metadata in the
in-memory auth object, and redacts the secret from debug output. Legacy
Agent Identity JWT handling remains unchanged. The shared access-token
classifier lives in a private neutral module because it dispatches
between both credential types.

PAT hydration fails closed when AuthAPI omits any required metadata,
including email. Hydrated metadata is intentionally not persisted:
startup performs a live `whoami` preflight so revoked tokens or changed
account metadata are not accepted from a stale cache.

## Workspace restriction scope

This change intentionally does **not** apply
`forced_chatgpt_workspace_id` to PAT authentication. The setting is a
client-side config guardrail, not an authorization boundary, and PAT
does not currently require workspace-ID parity. The PAT login and
`CODEX_ACCESS_TOKEN` paths therefore validate through AuthAPI without
threading workspace-restriction state through access-token loading.
Existing workspace checks for non-PAT auth remain on their established
paths.

## App-server compatibility

The public app-server `AuthMode` is shared across v1 and v2, and
PAT-backed auth reports `personalAccessToken` through both APIs.
Following human review, this intentionally removes the temporary v1
compatibility mapping that reported PATs as `chatgpt`; the deprecated v1
API is kept in parity with v2 rather than maintaining a separate closed
enum. Clients with exhaustive auth-mode handling in either API version
must add the new case and should generally treat it as ChatGPT-backed
unless they need PAT-specific behavior.

The v1 auth-status response still omits the raw PAT when `includeToken`
is requested because that response cannot carry the account metadata
needed to reuse the credential safely. Persisted PAT auth also omits the
new enum value so older Codex builds can deserialize `auth.json` and
infer PAT auth from the credential field after a rollback.

## Validation

Latest review-fix validation:

- `CARGO_INCREMENTAL=0 just test -p codex-login` (126 passed)
- `CARGO_INCREMENTAL=0 just test -p codex-cli` (263 passed)
- `CARGO_INCREMENTAL=0 just test -p codex-cli
stored_auth_validation_handles_personal_access_token`
- `CARGO_INCREMENTAL=0 just test -p codex-app-server-protocol` (226
passed)
- `CARGO_INCREMENTAL=0 just test -p codex-models-manager
refresh_available_models_uses_remote_only_catalog_for_chatgpt_auth`
- `CARGO_INCREMENTAL=0 just test -p codex-tui
existing_non_oauth_chatgpt_login_counts_as_signed_in`
- `CARGO_INCREMENTAL=0 just fix -p codex-login -p
codex-app-server-protocol -p codex-models-manager -p codex-tui -p
codex-cli`
- `just fmt`
- `git diff --check`

The broader `codex-tui` suite previously compiled and ran 2,834 tests.
Three unrelated environment-sensitive guardian/IDE-socket tests failed
after retries; the PAT-relevant TUI coverage passed.
2026-06-05 17:36:18 -07:00
cooper-oai e5afe5bf8c [codex-cli] Refresh near-expiry ChatGPT access tokens before requests (#23546)
## Summary

- refresh managed ChatGPT auth during auth resolution when its access
token is inside ChatGPT web's five-minute near-expiry window
- cover refresh-window decisions while preserving the existing
expired-token refresh path

## Why

Codex already resolves managed ChatGPT auth before outbound requests and
refreshes expired access tokens there. This change adjusts the existing
predicate to refresh a still-valid access token once it is within the
same five-minute refresh window used by ChatGPT web, avoiding a request
with a token about to expire.

A cross-process serialization follow-up was explored in #24663 and
closed for now; we do not currently suspect cross-process refresh races
are a root cause of the refresh errors under investigation.

External-token, API-key, and Agent Identity auth modes remain unchanged.

## Validation

- `bazel test //codex-rs/login:login-all-test`
- `just fmt` runs Rust formatting successfully, then its Python SDK Ruff
step cannot install `openai-codex-cli-bin==0.131.0a4` on this Linux
environment because no compatible wheel is published.
2026-05-28 05:40:17 -07:00
cooper-oaiandCodex 54ec99cb54 [login] revoke superseded auth tokens on relogin (#21747)
## Summary
- revoke previously stored managed ChatGPT tokens after a successful
re-login
- keep the new login successful even when revocation is unavailable or
fails
- cover the shared persistence path used by browser and device-code
login flows

## Why
A new `codex login` currently overwrites existing managed ChatGPT
credentials without attempting to revoke the superseded tokens, leaving
old credentials valid longer than necessary.

## Validation
- `just fmt`
- `CARGO_HOME=/tmp/cargo-home cargo test -p codex-login`

## Notes
- Initial local Cargo validation hit a corrupt existing crate cache in
the default `CARGO_HOME`; rerunning with a clean temporary `CARGO_HOME`
passed.

---------

Co-authored-by: Codex <noreply@openai.com>
2026-05-11 13:36:46 -07:00