Skip Guardian reviews in Full Access (#42147)

## Why

Full Access already combines `approvalPolicy: "never"` with unrestricted
permissions, so confirmation-only actions do not require a model review.

## What changed

- Detect Full Access consistently across the thread and every selected
  environment. Pending, failed, or restricted environments are not treated as
  Full Access.
- Approve confirmation-only Guardian and MCP requests without synchronous
  review, sampler prewarming, or background scoring. Cancellation, explicit
  denials, and forms that require user input keep their existing behavior.
- Re-evaluate the active permission state on each turn so an existing thread
  can enter or leave Full Access safely.

## Testing

Added coverage for switching approval modes, strict sensitive MCP
confirmations, failed and pending environments, and suppression of Guardian
requests and background-scoring connections.

GitOrigin-RevId: 089ab4296dde473b8e33ab8324be79c5446c46f6
This commit is contained in:
jif authored and copyberry committed 2026-09-01 22:16:54 +00:00
1 parent cd8bd62c6e
commit e576993911
14 files changed
+295 -22

No files matched your search

+24
View File
@@ -726,6 +726,30 @@ async fn review_guardian_mcp_elicitation(
};
let step_settings = turn_context.current_settings.load_full();
// Full Access skips inference, not the active-turn and cancellation checks.
if turn_context.environments.has_full_access(
turn_context.approval_policy(),
&turn_context
.config
.permissions
.effective_permission_profile(),
) && matches!(
&request.elicitation,
Elicitation::Mcp(rmcp::model::ElicitRequestParams::FormElicitationParams {
requested_schema, ..
}) if requested_schema.properties.is_empty()
) {
let decision = if cancellation_token.is_cancelled() {
ReviewDecision::Abort
} else {
ReviewDecision::Approved
};
return Ok(Some(mcp_elicitation_response_from_guardian_decision(
decision,
turn_context.model_info(),
)));
}
// The invocation identifies the tool event, but a nested elicitation can
// review a different action and connector than the enclosing JavaScript.
let originating_call_id = if is_node_repl_backed_server(&request.server_name)
+7
View File
@@ -250,6 +250,13 @@ impl SessionConfiguration {
approval_policy: self.step_settings.approval_policy.value(),
approvals_reviewer: self.step_settings.approvals_reviewer,
permission_profile: self.effective_permission_profile(&environment_selections),
full_access: codex_protocol::protocol::has_full_access(
self.step_settings.approval_policy.value(),
&self.permission_profile(),
environment_selections
.iter()
.map(|environment| &environment.config),
),
active_permission_profile: permission_profile.active_permission_profile(),
environments: TurnEnvironmentSelections::new(
self.legacy_fallback_cwd.clone(),
@@ -647,10 +647,13 @@ async fn strict_auto_review_turn_grant_forces_guardian_for_exec_command_policy_s
.set(AskForApproval::Never)
.expect("test setup should allow updating approval policy");
let mut config = (*turn_context_raw.config).clone();
// Keep Never outside Full Access without requiring an OS sandbox for this routing test.
config
.permissions
.set_permission_profile(codex_protocol::models::PermissionProfile::Disabled)
.expect("test setup should allow disabling the permission profile");
.set_permission_profile(codex_protocol::models::PermissionProfile::External {
network: NetworkSandboxPolicy::Restricted,
})
.expect("test setup should allow external sandbox permissions");
let TurnEnvironmentState::Ready(environment) =
&mut turn_context_raw.environments.environments[0]
else {