Honor attachment-owned permissions for MCP servers (#40728)

## Why

MCP servers attached to executor environments must retain their owner's
permission profile instead of inheriting the thread-wide sandbox authority.

## What changed

- Resolve and capture a permission profile for each enabled MCP server when
  publishing the runtime, and reject calls or elicitations when that authority
  is unavailable.
- Use the captured server profile for tool approval decisions, elicitation, and
  sandbox metadata, including after runtime refreshes.
- Materialize `:workspace_roots` from `PathUri` values so permissions preserve
  the path convention of remote executor environments.
- Give threadless app discovery and resource reads an explicit default
  permission profile.

## Testing

Added coverage for per-server elicitation authority, unresolved attachments,
runtime refreshes, restricted tool calls, and foreign-platform workspace roots.

GitOrigin-RevId: 6b188d4b08b29e9971ff7aa68a7785a0cdf9e394
This commit is contained in:
sayan-oai authored and copyberry committed 2026-08-25 22:50:49 +00:00
1 parent 0b94751cc4
commit 4213b38f3c
25 files changed
+805 -136

No files matched your search

+24 -4
View File
@@ -178,6 +178,24 @@ impl Session {
return;
};
loop {
let environments = self.services.turn_environments.snapshot().await;
let ready_environments = environments
.turn_environments()
.map(|environment| {
(
environment.selection.environment_id.clone(),
Arc::clone(&environment.environment),
)
})
.collect();
// Attachment resolution can finish after the owner's configuration callback.
if !self
.services
.mcp_runtime
.current_environments_match(&ready_environments)
{
self.mark_mcp_runtime_dirty();
}
let auth = self.services.auth_manager.auth_cached();
if !self
.services
@@ -232,9 +250,6 @@ impl Session {
)
.await;
refresh_invalidation.published = true;
if !self.mcp_refresh.is_pending() {
return;
}
}
}
@@ -813,9 +828,14 @@ async fn review_guardian_mcp_elicitation(
let approval_policy = mcp_config.approval_policy.value();
match approval_policy {
AskForApproval::Never => {
let Some(permission_profile) =
mcp_config.permission_profile_for_server(&request.server_name)
else {
return Ok(Some(mcp_elicitation_decline_without_message()));
};
if codex_mcp::mcp_permission_prompt_is_auto_approved(
approval_policy,
&mcp_config.permission_profile,
permission_profile,
codex_mcp::McpPermissionPromptAutoApproveContext::default(),
) && matches!(
&request.elicitation,