From 1a7eeaaea85fdb749946dcac361335aee604ff5f Mon Sep 17 00:00:00 2001 From: rreichel3-oai Date: Tue, 12 May 2026 10:56:03 -0400 Subject: [PATCH] Restore agent identity workspace enforcement Update enforced ChatGPT workspace checks so AgentIdentity credentials are compared against the configured workspace allowlist instead of skipping workspace enforcement. Add a regression test for AgentIdentity credentials that belong to a disallowed workspace and verify the auth file is removed. --- codex-rs/login/src/auth/auth_tests.rs | 61 +++++++++++++++++++++++++++ codex-rs/login/src/auth/manager.rs | 33 ++++++++------- 2 files changed, 79 insertions(+), 15 deletions(-) diff --git a/codex-rs/login/src/auth/auth_tests.rs b/codex-rs/login/src/auth/auth_tests.rs index 4b46b8c0a4..cceaffeef3 100644 --- a/codex-rs/login/src/auth/auth_tests.rs +++ b/codex-rs/login/src/auth/auth_tests.rs @@ -894,6 +894,67 @@ async fn enforce_login_restrictions_allows_any_matching_workspace_in_list() { .expect("any matching workspace in the allowed list should succeed"); } +#[tokio::test] +#[serial(codex_auth_env)] +async fn enforce_login_restrictions_logs_out_for_agent_identity_workspace_mismatch() { + let codex_home = tempdir().unwrap(); + let _access_token_guard = remove_access_token_env_var(); + let record = agent_identity_record("org_another_org"); + let agent_identity = + signed_agent_identity_jwt(&record, json!(record.plan_type)).expect("signed agent identity"); + let server = MockServer::start().await; + Mock::given(method("GET")) + .and(path("/backend-api/wham/agent-identities/jwks")) + .respond_with(ResponseTemplate::new(200).set_body_json(test_jwks_body())) + .expect(1) + .mount(&server) + .await; + Mock::given(method("POST")) + .and(path("/backend-api/v1/agent/agent-runtime-id/task/register")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "task_id": "task-123", + }))) + .expect(1) + .mount(&server) + .await; + let chatgpt_base_url = format!("{}/backend-api", server.uri()); + let _authapi_guard = + EnvVarGuard::set("CODEX_AGENT_IDENTITY_AUTHAPI_BASE_URL", &chatgpt_base_url); + save_auth( + codex_home.path(), + &AuthDotJson { + auth_mode: Some(ApiAuthMode::AgentIdentity), + openai_api_key: None, + tokens: None, + last_refresh: None, + agent_identity: Some(agent_identity), + }, + AuthCredentialsStoreMode::File, + ) + .expect("seed agent identity auth"); + + let config = AuthConfig { + codex_home: codex_home.path().to_path_buf(), + auth_credentials_store_mode: AuthCredentialsStoreMode::File, + forced_login_method: None, + forced_chatgpt_workspace_id: Some(vec!["org_mine".to_string()]), + chatgpt_base_url: Some(chatgpt_base_url), + }; + + let err = super::enforce_login_restrictions(&config) + .await + .expect_err("expected workspace mismatch to error"); + assert!( + err.to_string() + .contains("current credentials belong to org_another_org") + ); + assert!( + !codex_home.path().join("auth.json").exists(), + "auth.json should be removed on mismatch" + ); + server.verify().await; +} + #[tokio::test] async fn enforce_login_restrictions_allows_api_key_if_login_method_not_set_but_forced_chatgpt_workspace_id_is_set() { diff --git a/codex-rs/login/src/auth/manager.rs b/codex-rs/login/src/auth/manager.rs index 0483a72d93..a2e4e8e0d8 100644 --- a/codex-rs/login/src/auth/manager.rs +++ b/codex-rs/login/src/auth/manager.rs @@ -654,25 +654,28 @@ pub async fn enforce_login_restrictions(config: &AuthConfig) -> std::io::Result< } if let Some(expected_account_ids) = config.forced_chatgpt_workspace_id.as_deref() { - if !auth.is_chatgpt_auth() { - return Ok(()); - } - - let token_data = match auth.get_token_data() { - Ok(data) => data, - Err(err) => { - return logout_with_message( - &config.codex_home, - format!( - "Failed to load ChatGPT credentials while enforcing workspace restrictions: {err}. Logging out." - ), - config.auth_credentials_store_mode, - ); + let chatgpt_account_id = match &auth { + CodexAuth::ApiKey(_) => return Ok(()), + CodexAuth::AgentIdentity(_) => auth.get_account_id(), + CodexAuth::Chatgpt(_) | CodexAuth::ChatgptAuthTokens(_) => { + let token_data = match auth.get_token_data() { + Ok(data) => data, + Err(err) => { + return logout_with_message( + &config.codex_home, + format!( + "Failed to load ChatGPT credentials while enforcing workspace restrictions: {err}. Logging out." + ), + config.auth_credentials_store_mode, + ); + } + }; + token_data.id_token.chatgpt_account_id } }; // workspace is the external identifier for account id. - let chatgpt_account_id = token_data.id_token.chatgpt_account_id.as_deref(); + let chatgpt_account_id = chatgpt_account_id.as_deref(); if !chatgpt_account_id.is_some_and(|actual| { expected_account_ids .iter()