diff --git a/codex-rs/app-server/src/request_processors/account_processor.rs b/codex-rs/app-server/src/request_processors/account_processor.rs index 3224e57fe0..b2df78a545 100644 --- a/codex-rs/app-server/src/request_processors/account_processor.rs +++ b/codex-rs/app-server/src/request_processors/account_processor.rs @@ -284,6 +284,9 @@ impl AccountRequestProcessor { request_id: ConnectionRequestId, params: LoginAccountParams, ) -> Result<(), JSONRPCErrorError> { + if self.auth_manager.is_workload_identity_selected() { + return Err(self.configured_auth_owned_by_host_error()); + } match params { LoginAccountParams::ApiKey { api_key } => { self.login_api_key_v2(request_id, LoginApiKeyParams { api_key }) @@ -341,6 +344,12 @@ impl AccountRequestProcessor { ) } + fn configured_auth_owned_by_host_error(&self) -> JSONRPCErrorError { + invalid_request( + "Configured external authentication is owned by the app-server host and cannot be changed through account RPCs.", + ) + } + async fn login_api_key_common( &self, params: &LoginApiKeyParams, @@ -868,6 +877,9 @@ impl AccountRequestProcessor { } async fn logout_common(&self) -> std::result::Result, JSONRPCErrorError> { + if self.auth_manager.is_workload_identity_selected() { + return Err(self.configured_auth_owned_by_host_error()); + } let managed_bedrock_auth = matches!( self.auth_manager.auth_cached(), Some(CodexAuth::BedrockApiKey(_)) @@ -986,30 +998,31 @@ impl AccountRequestProcessor { let permanent_refresh_failure = self.auth_manager.refresh_failure_for_auth(&auth).is_some(); let auth_mode = auth_mode_to_api(auth.api_auth_mode()); - let (reported_auth_method, token_opt) = if matches!( - auth, - CodexAuth::Headers(_) - | CodexAuth::AgentIdentity(_) - | CodexAuth::PersonalAccessToken(_) - ) || include_token - && permanent_refresh_failure - { - // This response cannot represent the metadata needed to reuse these - // credentials. - (Some(auth_mode), None) - } else { - match auth.get_token() { - Ok(token) if !token.is_empty() => { - let tok = if include_token { Some(token) } else { None }; - (Some(auth_mode), tok) + let (reported_auth_method, token_opt) = + if self.auth_manager.is_workload_identity_selected() + || matches!( + auth, + CodexAuth::Headers(_) + | CodexAuth::AgentIdentity(_) + | CodexAuth::PersonalAccessToken(_) + ) + || include_token && permanent_refresh_failure + { + // Host-owned and metadata-bearing credentials are never exported. + (Some(auth_mode), None) + } else { + match auth.get_token() { + Ok(token) if !token.is_empty() => { + let tok = if include_token { Some(token) } else { None }; + (Some(auth_mode), tok) + } + Ok(_) => (None, None), + Err(err) => { + tracing::warn!("failed to get token for auth status: {err}"); + (None, None) + } } - Ok(_) => (None, None), - Err(err) => { - tracing::warn!("failed to get token for auth status: {err}"); - (None, None) - } - } - }; + }; GetAuthStatusResponse { auth_method: reported_auth_method, auth_token: token_opt, diff --git a/codex-rs/login/src/auth/manager.rs b/codex-rs/login/src/auth/manager.rs index f2fb4f465f..6272912b16 100644 --- a/codex-rs/login/src/auth/manager.rs +++ b/codex-rs/login/src/auth/manager.rs @@ -2612,6 +2612,10 @@ impl AuthManager { self.external_auth_provider().is_some() } + pub fn is_workload_identity_selected(&self) -> bool { + self.workload_identity_selected + } + pub fn is_external_chatgpt_auth_active(&self) -> bool { self.auth_cached() .as_ref()