openmoat_core/ir/
check.rs1use super::{Access, Effect, Enforcement};
5use crate::action::AtomicAction;
6use crate::kind::Kind;
7use crate::pattern::{GlobPattern, any_match};
8use crate::policy::PolicyError;
9
10#[derive(Debug)]
12pub struct Checker {
13 read_allowances: Vec<GlobPattern>,
15 read: CompiledAccess,
16 write: CompiledAccess,
17 net: CompiledAccess,
18 fetch: CompiledAccess,
19}
20
21#[derive(Debug)]
22struct CompiledAccess {
23 default: Effect,
24 deny: Vec<Vec<GlobPattern>>,
25 allow: Vec<Vec<GlobPattern>>,
26}
27
28impl Enforcement {
29 pub fn checker(&self) -> Result<Checker, PolicyError> {
31 let paths = self.fs.case_insensitive;
32 Ok(Checker {
33 read_allowances: self
34 .allowances
35 .iter()
36 .filter(|a| a.kind == Kind::FsRead)
37 .flat_map(|a| &a.patterns)
38 .map(|p| GlobPattern::compile(p, paths))
39 .collect::<Result<_, _>>()?,
40 read: CompiledAccess::compile(&self.fs.read, paths)?,
41 write: CompiledAccess::compile(&self.fs.write, paths)?,
42 net: CompiledAccess::compile(&self.egress.net, true)?,
43 fetch: CompiledAccess::compile(&self.egress.fetch, true)?,
44 })
45 }
46}
47
48impl Checker {
49 #[must_use]
51 pub fn check(&self, atom: &AtomicAction) -> Option<Effect> {
52 let access = match atom.kind() {
53 Kind::FsRead => &self.read,
54 Kind::FsWrite => &self.write,
55 Kind::Net => &self.net,
56 Kind::Fetch => &self.fetch,
57 Kind::Shell
58 | Kind::WebSearch
59 | Kind::UnknownTool
60 | Kind::EnvRead
61 | Kind::EnvSet
62 | Kind::Mcp => {
63 return None;
64 }
65 };
66 Some(access.effect(atom.subject()?))
67 }
68
69 #[must_use]
72 pub fn check_os(&self, atom: &AtomicAction) -> Option<Effect> {
73 let effect = self.check(atom)?;
74 let allowed = atom.kind() == Kind::FsRead
75 && !self.read.denies(atom.subject()?)
76 && any_match(&self.read_allowances, atom.subject()?);
77 Some(if allowed { Effect::Allow } else { effect })
78 }
79}
80
81impl CompiledAccess {
82 fn compile(access: &Access, case_insensitive: bool) -> Result<Self, PolicyError> {
83 let compile = |rules: &[super::Rule]| {
84 rules
85 .iter()
86 .map(|rule| {
87 rule.patterns
88 .iter()
89 .map(|p| GlobPattern::compile(p, case_insensitive))
90 .collect::<Result<Vec<_>, _>>()
91 })
92 .collect::<Result<Vec<_>, _>>()
93 };
94 Ok(Self {
95 default: access.default,
96 deny: compile(&access.deny)?,
97 allow: compile(&access.allow)?,
98 })
99 }
100
101 fn denies(&self, subject: &str) -> bool {
102 self.deny.iter().any(|r| any_match(r, subject))
103 }
104
105 fn effect(&self, subject: &str) -> Effect {
106 let hit = |rules: &[Vec<GlobPattern>]| rules.iter().any(|r| any_match(r, subject));
107 if self.denies(subject) {
108 Effect::Deny
109 } else if hit(&self.allow) {
110 Effect::Allow
111 } else {
112 self.default
113 }
114 }
115}