Bind unified exec approvals to shell executables (#39311)

## Why

An unfamiliar executable can ignore its arguments, so trust in an apparent
inner command must not implicitly trust the executable that runs it.

## What changed

- Evaluate unfamiliar shell executables alongside their parsed commands when
  determining unified exec approval requirements. Inner commands can add
  restrictions, but cannot grant trust to the executable.
- Include the executable in reusable approval keys so approval for one custom
  shell does not apply to another.
- Parse literal PowerShell scripts without launching the requested executable,
  preserving command policy checks before approval.

## Testing

Add cross-platform coverage for spoofed shell paths, allowed and forbidden
inner commands, explicit custom-shell approval, and session approval isolation.

GitOrigin-RevId: 0dd2c7e9a2ac30965ef5fe5de1a8d2968bb5f9d0
This commit is contained in:
jif
2026-08-18 23:00:53 +00:00
committed by copyberry
parent 280d56b1d8
commit 7d9990fa30
11 changed files with 606 additions and 21 deletions
@@ -1238,24 +1238,34 @@ impl UnifiedExecProcessManager {
};
let mut orchestrator = ToolOrchestrator::new();
let mut runtime = UnifiedExecRuntime::new(self, request.shell_mode.clone());
let session_shell = context.session.user_shell();
let configured_shell = request
.turn_environment
.shell
.as_ref()
.unwrap_or(session_shell.as_ref());
let exec_approval_requirement = context
.session
.services
.exec_policy
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
command: &request.command,
approval_policy: turn.approval_policy(),
permission_profile: request.turn_environment.permission_profile().clone(),
environment_policy: request.turn_environment.config().exec_policy.as_ref(),
windows_sandbox_level: turn.windows_sandbox_level,
sandbox_permissions: if request.additional_permissions_preapproved {
crate::sandboxing::SandboxPermissions::UseDefault
} else {
request.sandbox_permissions
.create_exec_approval_requirement_for_shell(
ExecApprovalRequest {
command: &request.command,
approval_policy: turn.approval_policy(),
permission_profile: request.turn_environment.permission_profile().clone(),
environment_policy: request.turn_environment.config().exec_policy.as_ref(),
windows_sandbox_level: turn.windows_sandbox_level,
sandbox_permissions: if request.additional_permissions_preapproved {
crate::sandboxing::SandboxPermissions::UseDefault
} else {
request.sandbox_permissions
},
prefix_rule: request.prefix_rule.clone(),
allow_prefix_rules: context.step_context.turn.allow_prefix_rules(),
},
prefix_rule: request.prefix_rule.clone(),
allow_prefix_rules: context.step_context.turn.allow_prefix_rules(),
})
configured_shell,
&request.shell_mode,
)
.await;
let req = UnifiedExecToolRequest {
command: request.command.clone(),