Reload remote executor credentials safely across token rotation (#58251)

## Why

ChatGPT credentials can rotate while a remote executor is idle. Registry requests need to follow stored rotations without continuing after logout or switching the executor's identity. Concurrent refreshes must also avoid overwriting newer credentials.

## What changed

- Reload and validate ChatGPT credentials before registry requests, pinning the credential mode, account, user, and workspace policy. Reuse cached metadata for unchanged personal access tokens.
- Retry registration when temporary credential resolution failures prevent a request from being sent. Bound credential resolution and block reuse of a token generation whose refresh outcome is unknown.
- Restrict supported authentication modes before executor startup loads credentials or cloud configuration.
- Serialize persistent credential writes across cooperating processes and compare stored credentials before committing refreshed tokens.
- Replace `auth.json` atomically so overlapping readers see a complete document, preserving symlink targets and protecting file permissions, including Windows access controls.

## Testing

Add coverage for credential rotation, logout, identity changes, cancelled refresh callers, temporary metadata failures, registration retries, concurrent credential writes, and atomic file replacement.

GitOrigin-RevId: 6b210944bf8eba2f45bc27113f901d2c3cb06b48
This commit is contained in:
tgrue-openai
2026-10-30 05:30:20 +00:00
committed by copyberry
parent c15d0ce323
commit 582a8812e0
25 changed files with 2178 additions and 103 deletions
+39 -7
View File
@@ -1,7 +1,7 @@
//! Loads executor startup configuration and remote authentication without changing CLI policy.
use codex_app_server::in_process::EmbeddedNetworkPolicy;
use codex_cloud_config::chatgpt_config_bundle_loader_for_storage;
use codex_cloud_config::chatgpt_config_bundle_loader;
use codex_config::LoaderOverrides;
use codex_core::config::ConfigBuilder;
use codex_core::config::ConfigLoadOptions;
@@ -11,10 +11,20 @@ use codex_core::config::load_config_toml_with_layer_stack;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_login::read_codex_access_token_from_env;
use codex_protocol::auth::AuthMode;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_cli::CliConfigOverrides;
use std::sync::Arc;
use crate::exec_server_auth;
const REMOTE_AUTH_MODES: &[AuthMode] = &[
AuthMode::ApiKey,
AuthMode::Chatgpt,
AuthMode::ChatgptAuthTokens,
AuthMode::PersonalAccessToken,
];
pub(super) async fn load_exec_server_remote_auth_provider(
config: &codex_core::config::Config,
base_url: &str,
@@ -53,11 +63,16 @@ pub(super) async fn load_exec_server_remote_auth_provider(
validate_api_key_remote_host(base_url)?;
}
if auth.is_chatgpt_auth() || auth_manager.is_workload_identity_selected() {
if auth_manager.is_workload_identity_selected() {
Ok(codex_model_provider::auth_provider_from_auth_manager(
auth_manager,
&auth,
))
} else if auth.is_chatgpt_auth() {
Ok(exec_server_auth::chatgpt_auth_provider(
auth_manager,
&auth,
)?)
} else {
Ok(codex_model_provider::auth_provider_from_auth(&auth))
}
@@ -129,12 +144,25 @@ pub(super) async fn load_exec_server_config(
},
)
.await?;
let bootstrap_auth_config = bootstrap_auth_config(&codex_home, &bootstrap_config)?;
let cloud_config_bundle = chatgpt_config_bundle_loader_for_storage(
network_policy.bind_bootstrap_auth(bootstrap_auth_config),
let bootstrap_auth_config = network_policy
.bind_bootstrap_auth(bootstrap_auth_config(&codex_home, &bootstrap_config)?);
let auth_manager = AuthManager::shared_from_auth_config_with_auth_modes(
bootstrap_auth_config.clone(),
/*enable_codex_api_key_env*/ true,
REMOTE_AUTH_MODES.to_vec(),
)
.await?;
let cloud_config_bundle = chatgpt_config_bundle_loader(
auth_manager,
bootstrap_auth_config
.chatgpt_base_url
.unwrap_or_else(|| "https://chatgpt.com/backend-api/".to_string()),
bootstrap_auth_config.codex_home,
bootstrap_auth_config
.auth_route_config
.http_client_factory()
.clone(),
);
builder = builder.cloud_config_bundle(cloud_config_bundle);
}
let mut config = builder.build().await?;
@@ -146,8 +174,12 @@ async fn load_exec_server_remote_auth(
config: &codex_core::config::Config,
missing_auth_error: &'static str,
) -> anyhow::Result<(Arc<AuthManager>, codex_login::CodexAuth)> {
let auth_manager =
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ true).await?;
let auth_manager = AuthManager::shared_from_config_with_auth_modes(
config,
/*enable_codex_api_key_env*/ true,
REMOTE_AUTH_MODES.to_vec(),
)
.await?;
let auth = match auth_manager.auth().await {
Some(auth) => auth,