Extend runtime auth to account, plugin, and file requests (#59564)

## What changed

- Use the shared runtime and effective configuration for account API clients and remote plugin requests, including standalone plugin commands.
- Support dual-header and assertion-only authentication for remote plugins and MCP file uploads when runtime agent instances are enabled.
- Keep plugin credentials and caches scoped to the thread's account when resuming with different credentials. Bind request pools and multi-step plugin storage operations to the selected account so account replacement revokes stale operations.
- Protect runtime credentials on cross-origin redirects while preserving configured routing policies and legacy authentication for custom backends.
- Apply server-provided `upload_headers` to file storage uploads.

## Testing

Add coverage for shared runtime registration, both authentication modes, configuration reloads, resumed-thread account selection, account replacement during plugin uploads and downloads, and credential stripping on redirects. Extend file upload tests to verify runtime assertions and storage-only upload headers.

GitOrigin-RevId: 68aef81051a6f9bf919fffda52f372b108fce805
This commit is contained in:
cooper-oai
2026-11-05 03:49:13 +00:00
committed by copyberry
parent c90354172c
commit 5c0b1ac71f
55 changed files with 2145 additions and 252 deletions
+56 -11
View File
@@ -99,6 +99,7 @@ use codex_protocol::protocol::TurnEnvironmentRequest;
use codex_protocol::protocol::TurnEnvironmentSelection;
use codex_protocol::protocol::W3cTraceContext;
use codex_rollout::state_db::StateDbHandle;
use codex_runtime_agent_instance::RuntimeAuthManager;
use codex_skills_extension::HostSkillsService;
use codex_thread_store::LoadThreadHistoryParams;
use codex_thread_store::LocalThreadStore;
@@ -599,6 +600,7 @@ impl ThreadManager {
codex_home.to_path_buf(),
restriction_product,
Arc::clone(&auth_manager),
Arc::clone(&runtime_auth),
skills_service.clone(),
));
let mcp_manager = Arc::new(McpManager::new_with_extensions(
@@ -809,10 +811,12 @@ impl ThreadManager {
/*bundled_skills_enabled*/ true,
restriction_product,
));
let runtime_auth = crate::runtime_auth::RuntimeAuthSlot::default();
let plugins_manager = Arc::new(PluginsManager::new_with_options(
codex_home.clone(),
restriction_product,
Arc::clone(&auth_manager),
Arc::clone(&runtime_auth),
skills_service.clone(),
));
let mcp_manager = Arc::new(McpManager::new(Arc::clone(&plugins_manager)));
@@ -829,7 +833,7 @@ impl ThreadManager {
let agent_graph_store = local_agent_graph_store_from_state_db(state_db.as_ref());
Self {
state: Arc::new(ThreadManagerState {
runtime_auth: Default::default(),
runtime_auth,
threads: Arc::new(RwLock::new(HashMap::new())),
shared_thread_instructions: Default::default(),
thread_created_tx,
@@ -882,6 +886,30 @@ impl ThreadManager {
self.state.auth_manager.clone()
}
/// Share the harness runtime with an API client using this effective ChatGPT configuration.
pub fn runtime_auth_for_config(
&self,
config: &Config,
) -> std::io::Result<Option<(Arc<RuntimeAuthManager>, codex_features::RuntimeAuthMode)>> {
if config
.features
.enabled(codex_features::Feature::RuntimeAgentInstances)
{
codex_model_provider::validate_runtime_destination(Some(&config.chatgpt_base_url))
.map_err(std::io::Error::other)?;
}
if !codex_runtime_agent_instance::is_openai_api_url(&config.chatgpt_base_url) {
return Ok(None);
}
Ok(crate::runtime_auth::runtime_for_config(
&self.state.runtime_auth,
config,
Arc::clone(&self.state.auth_manager),
&self.state.installation_id,
)
.map(|runtime| (runtime, config.runtime_auth_mode)))
}
pub fn skills_service(&self) -> Arc<HostSkillsService> {
self.state.skills_service.clone()
}
@@ -2577,20 +2605,37 @@ impl ThreadManagerState {
} else {
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile
};
// Resume may supply a different credential owner. Keep its runtime separate from the
// harness default, and let sessions using that parent's credentials inherit its owner.
let runtime_auth = if let Some(runtime_auth) = inherited_runtime_auth {
runtime_auth
} else if Arc::ptr_eq(&auth_manager, &self.auth_manager) {
Arc::clone(&self.runtime_auth)
// A resumed actor and its descendants share an owner, not the harness default's credentials.
let (runtime_auth, plugins_manager) = if Arc::ptr_eq(&auth_manager, &self.auth_manager) {
(
inherited_runtime_auth.unwrap_or_else(|| Arc::clone(&self.runtime_auth)),
Arc::clone(&self.plugins_manager),
)
} else {
let threads = self.threads.read().await;
parent_thread_id
if let Some(parent) = parent_thread_id
.or(forked_from_thread_id)
.and_then(|id| threads.get(&id))
.filter(|parent| Arc::ptr_eq(&parent.session.services.auth_manager, &auth_manager))
.map(|parent| Arc::clone(&parent.session.services.runtime_auth))
.unwrap_or_default()
{
(
inherited_runtime_auth
.unwrap_or_else(|| Arc::clone(&parent.session.services.runtime_auth)),
Arc::clone(&parent.session.services.plugins_manager),
)
} else {
let runtime_auth = inherited_runtime_auth.unwrap_or_default();
let plugins = Arc::new(
self.plugins_manager
.for_auth_context(Arc::clone(&auth_manager), Arc::clone(&runtime_auth)),
);
(runtime_auth, plugins)
}
};
let mcp_manager = if Arc::ptr_eq(&plugins_manager, &self.plugins_manager) {
mcp_manager
} else {
Arc::new(mcp_manager.for_plugins_manager(Arc::clone(&plugins_manager)))
};
let runtime = crate::runtime_auth::runtime_for_config(
&runtime_auth,
@@ -2621,7 +2666,7 @@ impl ThreadManagerState {
git_root_discovery: Arc::clone(&self.git_root_discovery),
environment_manager: Arc::clone(&self.environment_manager),
skills_service: Arc::clone(&self.skills_service),
plugins_manager: Arc::clone(&self.plugins_manager),
plugins_manager,
mcp_manager,
code_mode_session_provider: Arc::clone(&self.code_mode_session_provider),
extensions,