@@ -13,6 +13,7 @@ use codex_execpolicy::AmendError;
1313use codex_execpolicy:: Decision ;
1414use codex_execpolicy:: Error as ExecPolicyRuleError ;
1515use codex_execpolicy:: Evaluation ;
16+ use codex_execpolicy:: MatchOptions ;
1617use codex_execpolicy:: NetworkRuleProtocol ;
1718use codex_execpolicy:: Policy ;
1819use codex_execpolicy:: PolicyParser ;
@@ -221,14 +222,22 @@ impl ExecPolicyManager {
221222 used_complex_parsing,
222223 )
223224 } ;
224- let evaluation = exec_policy. check_multiple ( commands. iter ( ) , & exec_policy_fallback) ;
225+ let match_options = MatchOptions {
226+ resolve_host_executables : true ,
227+ } ;
228+ let evaluation = exec_policy. check_multiple_with_options (
229+ commands. iter ( ) ,
230+ & exec_policy_fallback,
231+ & match_options,
232+ ) ;
225233
226234 let requested_amendment = derive_requested_execpolicy_amendment_from_prefix_rule (
227235 prefix_rule. as_ref ( ) ,
228236 & evaluation. matched_rules ,
229237 exec_policy. as_ref ( ) ,
230238 & commands,
231239 & exec_policy_fallback,
240+ & match_options,
232241 ) ;
233242
234243 match evaluation. decision {
@@ -630,6 +639,7 @@ fn derive_requested_execpolicy_amendment_from_prefix_rule(
630639 exec_policy : & Policy ,
631640 commands : & [ Vec < String > ] ,
632641 exec_policy_fallback : & impl Fn ( & [ String ] ) -> Decision ,
642+ match_options : & MatchOptions ,
633643) -> Option < ExecPolicyAmendment > {
634644 let prefix_rule = prefix_rule?;
635645 if prefix_rule. is_empty ( ) {
@@ -656,6 +666,7 @@ fn derive_requested_execpolicy_amendment_from_prefix_rule(
656666 & amendment. command ,
657667 commands,
658668 exec_policy_fallback,
669+ match_options,
659670 ) {
660671 Some ( amendment)
661672 } else {
@@ -668,6 +679,7 @@ fn prefix_rule_would_approve_all_commands(
668679 prefix_rule : & [ String ] ,
669680 commands : & [ Vec < String > ] ,
670681 exec_policy_fallback : & impl Fn ( & [ String ] ) -> Decision ,
682+ match_options : & MatchOptions ,
671683) -> bool {
672684 let mut policy_with_prefix_rule = exec_policy. clone ( ) ;
673685 if policy_with_prefix_rule
@@ -679,7 +691,7 @@ fn prefix_rule_would_approve_all_commands(
679691
680692 commands. iter ( ) . all ( |command| {
681693 policy_with_prefix_rule
682- . check ( command, exec_policy_fallback)
694+ . check_with_options ( command, exec_policy_fallback, match_options )
683695 . decision
684696 == Decision :: Allow
685697 } )
@@ -849,6 +861,15 @@ mod tests {
849861 path. to_string_lossy ( ) . into_owned ( )
850862 }
851863
864+ fn host_program_path ( name : & str ) -> String {
865+ let executable_name = if cfg ! ( windows) {
866+ format ! ( "{name}.exe" )
867+ } else {
868+ name. to_string ( )
869+ } ;
870+ host_absolute_path ( & [ "usr" , "bin" , & executable_name] )
871+ }
872+
852873 fn starlark_string ( value : & str ) -> String {
853874 value. replace ( '\\' , "\\ \\ " ) . replace ( '"' , "\\ \" " )
854875 }
@@ -1398,6 +1419,115 @@ prefix_rule(
13981419 ) ;
13991420 }
14001421
1422+ #[ tokio:: test]
1423+ async fn absolute_path_exec_approval_requirement_matches_host_executable_rules ( ) {
1424+ let git_path = host_program_path ( "git" ) ;
1425+ let git_path_literal = starlark_string ( & git_path) ;
1426+ let policy_src = format ! (
1427+ r#"
1428+ host_executable(name = "git", paths = ["{git_path_literal}"])
1429+ prefix_rule(pattern=["git"], decision="allow")
1430+ "#
1431+ ) ;
1432+ let mut parser = PolicyParser :: new ( ) ;
1433+ parser
1434+ . parse ( "test.rules" , & policy_src)
1435+ . expect ( "parse policy" ) ;
1436+ let manager = ExecPolicyManager :: new ( Arc :: new ( parser. build ( ) ) ) ;
1437+ let command = vec ! [ git_path, "status" . to_string( ) ] ;
1438+
1439+ let requirement = manager
1440+ . create_exec_approval_requirement_for_command ( ExecApprovalRequest {
1441+ command : & command,
1442+ approval_policy : AskForApproval :: UnlessTrusted ,
1443+ sandbox_policy : & SandboxPolicy :: new_read_only_policy ( ) ,
1444+ sandbox_permissions : SandboxPermissions :: UseDefault ,
1445+ prefix_rule : None ,
1446+ } )
1447+ . await ;
1448+
1449+ assert_eq ! (
1450+ requirement,
1451+ ExecApprovalRequirement :: Skip {
1452+ bypass_sandbox: true ,
1453+ proposed_execpolicy_amendment: None ,
1454+ }
1455+ ) ;
1456+ }
1457+
1458+ #[ tokio:: test]
1459+ async fn absolute_path_exec_approval_requirement_ignores_disallowed_host_executable_paths ( ) {
1460+ let allowed_git_path = host_program_path ( "git" ) ;
1461+ let disallowed_git_path = host_absolute_path ( & [
1462+ "opt" ,
1463+ "homebrew" ,
1464+ "bin" ,
1465+ if cfg ! ( windows) { "git.exe" } else { "git" } ,
1466+ ] ) ;
1467+ let allowed_git_path_literal = starlark_string ( & allowed_git_path) ;
1468+ let policy_src = format ! (
1469+ r#"
1470+ host_executable(name = "git", paths = ["{allowed_git_path_literal}"])
1471+ prefix_rule(pattern=["git"], decision="prompt")
1472+ "#
1473+ ) ;
1474+ let mut parser = PolicyParser :: new ( ) ;
1475+ parser
1476+ . parse ( "test.rules" , & policy_src)
1477+ . expect ( "parse policy" ) ;
1478+ let manager = ExecPolicyManager :: new ( Arc :: new ( parser. build ( ) ) ) ;
1479+ let command = vec ! [ disallowed_git_path, "status" . to_string( ) ] ;
1480+
1481+ let requirement = manager
1482+ . create_exec_approval_requirement_for_command ( ExecApprovalRequest {
1483+ command : & command,
1484+ approval_policy : AskForApproval :: UnlessTrusted ,
1485+ sandbox_policy : & SandboxPolicy :: new_read_only_policy ( ) ,
1486+ sandbox_permissions : SandboxPermissions :: UseDefault ,
1487+ prefix_rule : None ,
1488+ } )
1489+ . await ;
1490+
1491+ assert_eq ! (
1492+ requirement,
1493+ ExecApprovalRequirement :: Skip {
1494+ bypass_sandbox: false ,
1495+ proposed_execpolicy_amendment: Some ( ExecPolicyAmendment :: new( command) ) ,
1496+ }
1497+ ) ;
1498+ }
1499+
1500+ #[ tokio:: test]
1501+ async fn requested_prefix_rule_can_approve_absolute_path_commands ( ) {
1502+ let command = vec ! [
1503+ host_program_path( "cargo" ) ,
1504+ "install" . to_string( ) ,
1505+ "cargo-insta" . to_string( ) ,
1506+ ] ;
1507+ let manager = ExecPolicyManager :: default ( ) ;
1508+
1509+ let requirement = manager
1510+ . create_exec_approval_requirement_for_command ( ExecApprovalRequest {
1511+ command : & command,
1512+ approval_policy : AskForApproval :: UnlessTrusted ,
1513+ sandbox_policy : & SandboxPolicy :: new_read_only_policy ( ) ,
1514+ sandbox_permissions : SandboxPermissions :: UseDefault ,
1515+ prefix_rule : Some ( vec ! [ "cargo" . to_string( ) , "install" . to_string( ) ] ) ,
1516+ } )
1517+ . await ;
1518+
1519+ assert_eq ! (
1520+ requirement,
1521+ ExecApprovalRequirement :: NeedsApproval {
1522+ reason: None ,
1523+ proposed_execpolicy_amendment: Some ( ExecPolicyAmendment :: new( vec![
1524+ "cargo" . to_string( ) ,
1525+ "install" . to_string( ) ,
1526+ ] ) ) ,
1527+ }
1528+ ) ;
1529+ }
1530+
14011531 #[ tokio:: test]
14021532 async fn exec_approval_requirement_respects_approval_policy ( ) {
14031533 let policy_src = r#"prefix_rule(pattern=["rm"], decision="prompt")"# ;
@@ -1952,6 +2082,7 @@ prefix_rule(
19522082 & Policy :: empty ( ) ,
19532083 & commands,
19542084 & |_: & [ String ] | Decision :: Allow ,
2085+ & MatchOptions :: default ( ) ,
19552086 )
19562087 }
19572088
0 commit comments