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
This commit is contained in:
cooper-oai
2026-08-13 19:29:37 +00:00
committed by copyberry
parent 781445f7c6
commit 990218bbbd
33 changed files with 368 additions and 212 deletions

View File

@@ -66,7 +66,7 @@ pub(crate) async fn chatgpt_get_request_with_timeout<T: DeserializeOwned>(
) -> anyhow::Result<T> {
let chatgpt_base_url = &config.chatgpt_base_url;
let auth_manager =
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await;
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await?;
let auth = auth_manager
.auth()
.await

View File

@@ -32,18 +32,18 @@ const DIRECTORY_CONNECTORS_TIMEOUT: Duration = Duration::from_secs(60);
const CONNECTOR_METADATA_TIMEOUT: Duration = Duration::from_secs(60);
const DEFAULT_APPS_PRODUCT_SKU: &str = "codex";
async fn apps_enabled(config: &Config) -> bool {
async fn apps_enabled(config: &Config) -> anyhow::Result<bool> {
let auth_manager =
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await;
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await?;
let auth = auth_manager.auth().await;
config
Ok(config
.features
.apps_enabled_for_auth(auth.as_ref().is_some_and(CodexAuth::uses_codex_backend))
.apps_enabled_for_auth(auth.as_ref().is_some_and(CodexAuth::uses_codex_backend)))
}
async fn connector_auth(config: &Config) -> anyhow::Result<CodexAuth> {
let auth_manager =
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await;
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await?;
let auth = auth_manager
.auth()
.await
@@ -56,7 +56,7 @@ async fn connector_auth(config: &Config) -> anyhow::Result<CodexAuth> {
}
pub async fn list_connectors(config: &Config) -> anyhow::Result<Vec<AppInfo>> {
if !apps_enabled(config).await {
if !apps_enabled(config).await? {
return Ok(Vec::new());
}
let (connectors_result, accessible_result) = tokio::join!(
@@ -82,7 +82,7 @@ pub async fn list_cached_all_connectors(
config: &Config,
plugin_apps: &[AppConnectorId],
) -> Option<Vec<AppInfo>> {
if !apps_enabled(config).await {
if !apps_enabled(config).await.ok()? {
return Some(Vec::new());
}
@@ -100,7 +100,7 @@ pub async fn list_all_connectors_with_options(
force_refetch: bool,
plugin_apps: &[AppConnectorId],
) -> anyhow::Result<Vec<AppInfo>> {
if !apps_enabled(config).await {
if !apps_enabled(config).await? {
return Ok(Vec::new());
}
let auth = connector_auth(config).await?;