diff --git a/codex-rs/execpolicy2/src/policy.rs b/codex-rs/execpolicy2/src/policy.rs index d453404b26..12416b050b 100644 --- a/codex-rs/execpolicy2/src/policy.rs +++ b/codex-rs/execpolicy2/src/policy.rs @@ -38,6 +38,28 @@ impl Policy { None => Evaluation::NoMatch, } } + + pub fn check_multiple(&self, commands: Commands) -> Evaluation + where + Commands: IntoIterator, + Commands::Item: AsRef<[String]>, + { + let matched_rules: Vec = commands + .into_iter() + .flat_map(|command| match self.check(command.as_ref()) { + Evaluation::Match { matched_rules, .. } => matched_rules, + Evaluation::NoMatch => Vec::new(), + }) + .collect(); + + match matched_rules.iter().map(RuleMatch::decision).max() { + Some(decision) => Evaluation::Match { + decision, + matched_rules, + }, + None => Evaluation::NoMatch, + } + } } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] diff --git a/codex-rs/execpolicy2/tests/basic.rs b/codex-rs/execpolicy2/tests/basic.rs index 702ea6c0a5..8506af05eb 100644 --- a/codex-rs/execpolicy2/tests/basic.rs +++ b/codex-rs/execpolicy2/tests/basic.rs @@ -254,3 +254,53 @@ prefix_rule( commit ); } + +#[test] +fn strictest_decision_across_multiple_commands() { + let policy_src = r#" +prefix_rule( + pattern = ["git", "status"], + decision = "allow", +) +prefix_rule( + pattern = ["git"], + decision = "prompt", +) +prefix_rule( + pattern = ["git", "commit"], + decision = "forbidden", +) + "#; + let policy = PolicyParser::parse("test.codexpolicy", policy_src).expect("parse policy"); + + let commands = vec![ + tokens(&["git", "status"]), + tokens(&["git", "commit", "-m", "hi"]), + ]; + + let evaluation = policy.check_multiple(&commands); + assert_eq!( + Evaluation::Match { + decision: Decision::Forbidden, + matched_rules: vec![ + RuleMatch::PrefixRuleMatch { + matched_prefix: tokens(&["git", "status"]), + decision: Decision::Allow, + }, + RuleMatch::PrefixRuleMatch { + matched_prefix: tokens(&["git"]), + decision: Decision::Prompt, + }, + RuleMatch::PrefixRuleMatch { + matched_prefix: tokens(&["git"]), + decision: Decision::Prompt, + }, + RuleMatch::PrefixRuleMatch { + matched_prefix: tokens(&["git", "commit"]), + decision: Decision::Forbidden, + }, + ], + }, + evaluation + ); +}