mirror of
https://github.com/openai/codex.git
synced 2026-10-27 16:02:59 +00:00
Isolate MCP OAuth credentials by environment (#36310)
## Why MCP servers running in an executor must not reuse host-owned OAuth credentials, even when they have the same configured server name and URL. ## What changed - Derive environment-scoped credential names for executor-owned MCP servers and use them consistently for login, logout, authentication, and connection setup. - Mark executor-owned entries in the file credential store and fail closed when loading or saving an entry could cross the host/executor boundary. - Preserve compatibility with existing local OAuth credentials, including local server names that overlap the new reserved prefixes. ## Testing - Extend the executor MCP integration test to verify that executor requests use the executor token, never send the host token, and persist both credentials separately. GitOrigin-RevId: 4fc92d0533b0fe2e0df34f6d47b81e3d20d07807
This commit is contained in:
@@ -169,8 +169,9 @@ pub(crate) async fn maybe_install_mcp_dependencies(
|
||||
oauth_config.discovered_scopes.clone(),
|
||||
);
|
||||
let oauth_client_id = server_config.oauth_client_id();
|
||||
let oauth_credential_name = server_config.oauth_credential_name(&name);
|
||||
let first_attempt = perform_oauth_login(
|
||||
&name,
|
||||
oauth_credential_name.as_ref(),
|
||||
&oauth_config.url,
|
||||
config.mcp_oauth_credentials_store_mode,
|
||||
config.auth_keyring_backend_kind(),
|
||||
@@ -188,7 +189,7 @@ pub(crate) async fn maybe_install_mcp_dependencies(
|
||||
if let Err(err) = first_attempt {
|
||||
if should_retry_without_scopes(&resolved_scopes, &err) {
|
||||
if let Err(err) = perform_oauth_login(
|
||||
&name,
|
||||
oauth_credential_name.as_ref(),
|
||||
&oauth_config.url,
|
||||
config.mcp_oauth_credentials_store_mode,
|
||||
config.auth_keyring_backend_kind(),
|
||||
|
||||
@@ -21,6 +21,7 @@ use codex_config::types::McpServerAuth;
|
||||
use codex_config::types::McpServerConfig;
|
||||
use codex_config::types::McpServerEnvVar;
|
||||
use codex_config::types::McpServerTransportConfig;
|
||||
use codex_config::types::OAuthCredentialsStoreMode;
|
||||
use codex_core::config::Config;
|
||||
use codex_exec_server::CreateDirectoryOptions;
|
||||
use codex_exec_server::Environment;
|
||||
@@ -3168,9 +3169,14 @@ async fn streamable_http_with_oauth_round_trip_impl() -> anyhow::Result<()> {
|
||||
// server so the test does not share credentials with other suite cases.
|
||||
let temp_home = Arc::new(tempdir()?);
|
||||
let _codex_home_guard = EnvVarGuard::set("CODEX_HOME", temp_home.path().as_os_str());
|
||||
let environment_id = remote_aware_environment_id();
|
||||
let credential_config: McpServerConfig = serde_json::from_value(json!({
|
||||
"url": &server_url,
|
||||
"environment_id": &environment_id,
|
||||
}))?;
|
||||
let credential_name = credential_config.oauth_credential_name(server_name);
|
||||
write_fallback_oauth_tokens(
|
||||
temp_home.path(),
|
||||
server_name,
|
||||
credential_name.as_ref(),
|
||||
&server_url,
|
||||
client_id,
|
||||
expected_token,
|
||||
@@ -3196,7 +3202,7 @@ async fn streamable_http_with_oauth_round_trip_impl() -> anyhow::Result<()> {
|
||||
env_http_headers: None,
|
||||
},
|
||||
TestMcpServerOptions {
|
||||
environment_id: remote_aware_environment_id(),
|
||||
environment_id,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
@@ -3628,7 +3634,6 @@ fn streamable_http_metadata_url(server_url: &str) -> String {
|
||||
}
|
||||
|
||||
fn write_fallback_oauth_tokens(
|
||||
home: &Path,
|
||||
server_name: &str,
|
||||
server_url: &str,
|
||||
client_id: &str,
|
||||
@@ -3641,21 +3646,25 @@ fn write_fallback_oauth_tokens(
|
||||
.duration_since(UNIX_EPOCH)?
|
||||
.as_millis() as u64;
|
||||
|
||||
let store = serde_json::json!({
|
||||
"stub": {
|
||||
"server_name": server_name,
|
||||
"server_url": server_url,
|
||||
"client_id": client_id,
|
||||
let tokens = serde_json::from_value(json!({
|
||||
"server_name": server_name,
|
||||
"url": server_url,
|
||||
"client_id": client_id,
|
||||
"token_response": {
|
||||
"access_token": access_token,
|
||||
"expires_at": expires_at,
|
||||
"token_type": "Bearer",
|
||||
"refresh_token": refresh_token,
|
||||
"scopes": ["profile"],
|
||||
}
|
||||
});
|
||||
"scope": "profile",
|
||||
},
|
||||
"expires_at": expires_at,
|
||||
}))?;
|
||||
|
||||
let file_path = home.join(".credentials.json");
|
||||
fs::write(&file_path, serde_json::to_vec(&store)?)?;
|
||||
Ok(())
|
||||
codex_rmcp_client::save_oauth_tokens(
|
||||
server_name,
|
||||
&tokens,
|
||||
OAuthCredentialsStoreMode::File,
|
||||
codex_config::types::AuthKeyringBackendKind::default(),
|
||||
)
|
||||
}
|
||||
|
||||
struct EnvVarGuard {
|
||||
|
||||
Reference in New Issue
Block a user