agcodex_execpolicy/
policy.rs

1use multimap::MultiMap;
2use regex_lite::Error as RegexError;
3use regex_lite::Regex;
4
5use crate::ExecCall;
6use crate::Forbidden;
7use crate::MatchedExec;
8use crate::NegativeExamplePassedCheck;
9use crate::ProgramSpec;
10use crate::error::Error;
11use crate::error::Result;
12use crate::policy_parser::ForbiddenProgramRegex;
13use crate::program::PositiveExampleFailedCheck;
14
15pub struct Policy {
16    programs: MultiMap<String, ProgramSpec>,
17    forbidden_program_regexes: Vec<ForbiddenProgramRegex>,
18    forbidden_substrings_pattern: Option<Regex>,
19}
20
21impl Policy {
22    pub fn new(
23        programs: MultiMap<String, ProgramSpec>,
24        forbidden_program_regexes: Vec<ForbiddenProgramRegex>,
25        forbidden_substrings: Vec<String>,
26    ) -> std::result::Result<Self, RegexError> {
27        let forbidden_substrings_pattern = if forbidden_substrings.is_empty() {
28            None
29        } else {
30            let escaped_substrings = forbidden_substrings
31                .iter()
32                .map(|s| regex_lite::escape(s))
33                .collect::<Vec<_>>()
34                .join("|");
35            Some(Regex::new(&format!("({escaped_substrings})"))?)
36        };
37        Ok(Self {
38            programs,
39            forbidden_program_regexes,
40            forbidden_substrings_pattern,
41        })
42    }
43
44    pub fn check(&self, exec_call: &ExecCall) -> Result<MatchedExec> {
45        let ExecCall { program, args } = &exec_call;
46        for ForbiddenProgramRegex { regex, reason } in &self.forbidden_program_regexes {
47            if regex.is_match(program) {
48                return Ok(MatchedExec::Forbidden {
49                    cause: Forbidden::Program {
50                        program: program.clone(),
51                        exec_call: exec_call.clone(),
52                    },
53                    reason: reason.clone(),
54                });
55            }
56        }
57
58        for arg in args {
59            if let Some(regex) = &self.forbidden_substrings_pattern
60                && regex.is_match(arg)
61            {
62                return Ok(MatchedExec::Forbidden {
63                    cause: Forbidden::Arg {
64                        arg: arg.clone(),
65                        exec_call: exec_call.clone(),
66                    },
67                    reason: format!("arg `{arg}` contains forbidden substring"),
68                });
69            }
70        }
71
72        let mut last_err = Err(Error::NoSpecForProgram {
73            program: program.clone(),
74        });
75        if let Some(spec_list) = self.programs.get_vec(program) {
76            for spec in spec_list {
77                match spec.check(exec_call) {
78                    Ok(matched_exec) => return Ok(matched_exec),
79                    Err(err) => {
80                        last_err = Err(err);
81                    }
82                }
83            }
84        }
85        last_err
86    }
87
88    pub fn check_each_good_list_individually(&self) -> Vec<PositiveExampleFailedCheck> {
89        let mut violations = Vec::new();
90        for (_program, spec) in self.programs.flat_iter() {
91            violations.extend(spec.verify_should_match_list());
92        }
93        violations
94    }
95
96    pub fn check_each_bad_list_individually(&self) -> Vec<NegativeExamplePassedCheck> {
97        let mut violations = Vec::new();
98        for (_program, spec) in self.programs.flat_iter() {
99            violations.extend(spec.verify_should_not_match_list());
100        }
101        violations
102    }
103}