Compare commits

...

1 Commits

Author SHA1 Message Date
David Wiesen
3c5a171c15 Honor PowerShell-wrapped execpolicy rules 2026-03-21 10:47:12 -07:00
3 changed files with 46 additions and 1 deletions

View File

@@ -35,6 +35,7 @@ use crate::bash::parse_shell_lc_single_command_prefix;
use crate::config::Config;
use crate::sandboxing::SandboxPermissions;
use crate::tools::sandboxing::ExecApprovalRequirement;
use codex_shell_command::command_safety::windows_safe_commands::try_parse_powershell_command_sequence;
use codex_utils_absolute_path::AbsolutePathBuf;
use shlex::try_join as shlex_try_join;
@@ -638,6 +639,12 @@ fn commands_for_exec_policy(command: &[String]) -> (Vec<Vec<String>>, bool) {
return (commands, false);
}
if let Some(commands) = try_parse_powershell_command_sequence(command)
&& !commands.is_empty()
{
return (commands, false);
}
if let Some(single_command) = parse_shell_lc_single_command_prefix(command) {
return (vec![single_command], true);
}

View File

@@ -1154,6 +1154,44 @@ async fn request_rule_falls_back_when_prefix_rule_does_not_approve_all_commands(
);
}
#[tokio::test]
async fn powershell_wrapped_allow_rule_bypasses_sandbox_prompt() {
if !cfg!(windows) && which::which("pwsh").is_err() {
return;
}
let policy_src = r#"prefix_rule(pattern=["git", "add"], decision="allow")"#;
let mut parser = PolicyParser::new();
parser
.parse("test.rules", policy_src)
.expect("parse policy");
let manager = ExecPolicyManager::new(Arc::new(parser.build()));
let command = vec![
"pwsh".to_string(),
"-Command".to_string(),
"git add -A".to_string(),
];
let requirement = manager
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
command: &command,
approval_policy: AskForApproval::OnRequest,
sandbox_policy: &SandboxPolicy::new_read_only_policy(),
file_system_sandbox_policy: &read_only_file_system_sandbox_policy(),
sandbox_permissions: SandboxPermissions::RequireEscalated,
prefix_rule: None,
})
.await;
assert_eq!(
requirement,
ExecApprovalRequirement::Skip {
bypass_sandbox: true,
proposed_execpolicy_amendment: None,
}
);
}
#[tokio::test]
async fn heuristics_apply_when_other_commands_match_policy() {
let policy_src = r#"prefix_rule(pattern=["apple"], decision="allow")"#;

View File

@@ -22,7 +22,7 @@ pub fn is_safe_command_windows(command: &[String]) -> bool {
/// Returns each command sequence if the invocation starts with a PowerShell binary.
/// For example, the tokens from `pwsh Get-ChildItem | Measure-Object` become two sequences.
fn try_parse_powershell_command_sequence(command: &[String]) -> Option<Vec<Vec<String>>> {
pub fn try_parse_powershell_command_sequence(command: &[String]) -> Option<Vec<Vec<String>>> {
let (exe, rest) = command.split_first()?;
if is_powershell_executable(exe) {
parse_powershell_invocation(exe, rest)