Compare commits

...

1 Commits

Author SHA1 Message Date
David Wiesen
7cd0cff0cf Handle PowerShell prefix rules in execpolicy 2026-03-21 10:39:30 -07:00
3 changed files with 50 additions and 1 deletions

View File

@@ -25,6 +25,7 @@ use codex_protocol::permissions::FileSystemSandboxKind;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::SandboxPolicy;
use codex_shell_command::command_safety::windows_safe_commands::try_parse_powershell_command_sequence;
use thiserror::Error;
use tokio::fs;
use tokio::task::spawn_blocking;
@@ -627,6 +628,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

@@ -556,6 +556,48 @@ fn commands_for_exec_policy_falls_back_for_whitespace_shell_script() {
assert_eq!(commands_for_exec_policy(&command), (vec![command], false));
}
#[tokio::test]
async fn powershell_single_command_prefix_rules_can_bypass_sandbox() {
if which::which("pwsh")
.or_else(|_| which::which("powershell"))
.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 command = vec![
"pwsh".to_string(),
"-NoProfile".to_string(),
"-Command".to_string(),
"git add -A".to_string(),
];
let requirement = ExecPolicyManager::new(Arc::new(parser.build()))
.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 evaluates_heredoc_script_against_prefix_rules() {
let policy_src = r#"prefix_rule(pattern=["python3"], 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)