Restrict enterprise MCP auth and fail closed on config refresh (#49260)

## Why

Configuration reloads must honor current managed restrictions without granting new enterprise MCP authority to existing sessions or overwriting newer configuration with stale snapshots.

## What changed

- Allow enterprise managed authentication only for configured MCP servers. Remove plugin `ema_auth` overlays from the schema and disable servers that still use them.
- Refresh ordinary MCP settings while preserving session settings and initially admitted enterprise registrations. Disable enterprise MCP when its registration changes, policy revokes access, or configuration loading or resolution fails; require a new session to restore enterprise authority.
- Publish refreshes only when their captured configuration is still current, with bounded retries in app-server reloads. Preserve updated managed restrictions even when a refresh is rejected.
- Finish processing other thread refreshes before `config/mcpServer/reload` reports a rejection, and document that an error can accompany partially applied changes.

## Testing

Add regression coverage for stale refreshes, bounded retries, managed-policy revocation, persistent enterprise disablement, ordinary MCP transport changes, and rejection of plugin enterprise authentication. Verify that denied enterprise servers receive no requests.

GitOrigin-RevId: 34429cd339c1ec152a2ee8697cb29f0cff5d9e5f
This commit is contained in:
Nick Steele
2026-09-29 13:08:37 +00:00
committed by copyberry
parent 4f16bdc265
commit af0d68a236
46 changed files with 1803 additions and 788 deletions
+39 -5
View File
@@ -161,6 +161,18 @@ pub struct CodexThreadSettingsOverrides {
pub disabled_plugin_ids: Option<Vec<String>>,
}
/// Result of publishing a loaded configuration snapshot for a thread.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[must_use]
pub enum ConfigRefreshOutcome {
/// The resolved configuration was published.
Published,
/// The captured owner changed; the caller must reload before retrying.
Stale,
/// Resolution failed and a configuration disabling enterprise MCP was published.
Rejected,
}
pub use codex_guardian_context::GuardianRootMessage;
/// Authorization state that changes on genuine user input or history resets.
@@ -985,14 +997,36 @@ impl CodexThread {
/// Refresh the thread's layer-backed user config state from a caller-supplied
/// config snapshot. Thread-scoped layers and session-static settings remain
/// unchanged.
pub async fn refresh_runtime_config(&self, next_config: crate::config::Config) {
self.session.refresh_runtime_config(next_config).await;
/// unchanged. A stale owner requires reloading from the current config before retrying.
pub async fn refresh_runtime_config(
&self,
expected_config: Arc<crate::config::Config>,
next_config: crate::config::Config,
) -> ConfigRefreshOutcome {
Box::pin(
self.session
.refresh_runtime_config(expected_config, next_config),
)
.await
}
/// Revokes enterprise MCP authority from the current owner after a failed reload.
pub async fn disable_mcp_enterprise_auth(&self) {
self.session.disable_mcp_enterprise_auth().await;
}
/// Refresh MCP configuration and managed requirements without reloading unrelated settings.
pub async fn refresh_mcp_config(&self, next_config: crate::config::Config) {
self.session.refresh_mcp_config(next_config).await;
/// A stale owner requires reloading from the current config before retrying.
pub async fn refresh_mcp_config(
&self,
expected_config: Arc<crate::config::Config>,
next_config: crate::config::Config,
) -> ConfigRefreshOutcome {
Box::pin(
self.session
.refresh_mcp_config(expected_config, next_config),
)
.await
}
/// Refreshes this thread's Apps tools before returning their runtime state.