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
+21
View File
@@ -3,6 +3,7 @@ use std::path::PathBuf;
use codex_utils_absolute_path::AbsolutePathBuf;
use crate::command_safety::try_parse_powershell_ast_commands;
use crate::command_safety::try_parse_powershell_commands;
use crate::shell_detect::ShellType;
use crate::shell_detect::detect_shell_type;
@@ -82,6 +83,14 @@ pub fn parse_powershell_command_into_plain_commands(
try_parse_powershell_ast_commands(executable, script)
}
/// Parse literal PowerShell commands without starting a PowerShell executable.
///
/// Unknown or dynamic syntax stays opaque so unfamiliar executables can be
/// evaluated without running them before approval.
pub fn parse_powershell_script_into_plain_commands(script: &str) -> Option<Vec<Vec<String>>> {
try_parse_powershell_commands(script)
}
/// This function attempts to find a powershell.exe executable on the system.
pub fn try_find_powershell_executable_blocking() -> Option<AbsolutePathBuf> {
try_find_powershellish_executable_in_path(&["powershell.exe"])
@@ -156,6 +165,7 @@ mod tests {
use super::extract_powershell_command;
#[cfg(windows)]
use super::parse_powershell_command_into_plain_commands;
use super::parse_powershell_script_into_plain_commands;
use super::prefix_powershell_script_with_utf8;
#[test]
@@ -205,6 +215,17 @@ mod tests {
assert_eq!(script, "Get-ChildItem | Select-String foo");
}
#[test]
fn parses_powershell_command_chains_and_preserves_windows_paths() {
assert_eq!(
parse_powershell_script_into_plain_commands(r"echo safe && Remove-Item C:\important"),
Some(vec![
vec!["echo".to_string(), "safe".to_string()],
vec!["Remove-Item".to_string(), r"C:\important".to_string()],
]),
);
}
#[test]
fn prefixes_powershell_command_with_best_effort_utf8() {
let cmd = vec![