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

+5 -3
View File
@@ -59,6 +59,7 @@ use codex_protocol::mcp_approval_meta::TOOL_DESCRIPTION_KEY as MCP_TOOL_APPROVAL
use codex_protocol::mcp_approval_meta::TOOL_PARAMS_DISPLAY_KEY as MCP_TOOL_APPROVAL_TOOL_PARAMS_DISPLAY_KEY;
use codex_protocol::mcp_approval_meta::TOOL_PARAMS_KEY as MCP_TOOL_APPROVAL_TOOL_PARAMS_KEY;
use codex_protocol::mcp_approval_meta::TOOL_TITLE_KEY as MCP_TOOL_APPROVAL_TOOL_TITLE_KEY;
use codex_protocol::models::PermissionProfile;
use codex_protocol::openai_models::InputModality;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::McpInvocation;
@@ -243,6 +244,7 @@ pub(crate) async fn handle_mcp_tool_call(
&hook_tool_name,
&metadata,
prepared_call.config(),
prepared_call.permission_profile(),
approval_policy,
)
.await
@@ -775,9 +777,8 @@ async fn augment_mcp_tool_request_meta_with_sandbox_state(
};
// TODO(anp): Build this metadata from the server's captured
// TurnEnvironment::sandbox_context instead of the runtime-wide Landlock value.
let permission_profile = prepared_call.config().permission_profile.clone();
let sandbox_state = serde_json::to_value(SandboxState {
permission_profile,
permission_profile: prepared_call.permission_profile().clone(),
codex_linux_sandbox_exe: prepared_call.config().codex_linux_sandbox_exe.clone(),
sandbox_cwd,
use_legacy_landlock: prepared_call.config().use_legacy_landlock,
@@ -1301,6 +1302,7 @@ async fn maybe_request_mcp_tool_approval(
hook_tool_name: &HookToolName,
metadata: &McpToolApprovalMetadata,
config: &codex_mcp::McpConfig,
permission_profile: &PermissionProfile,
policy: McpToolApprovalPolicy,
) -> Option<ReviewDecision> {
let turn_context = &step_context.turn;
@@ -1324,7 +1326,7 @@ async fn maybe_request_mcp_tool_approval(
if !strict_auto_review
&& mcp_permission_prompt_is_auto_approved(
config.approval_policy.value(),
&config.permission_profile,
permission_profile,
McpPermissionPromptAutoApproveContext {
tool_approval_mode: Some(policy.mode),
},