Skip to main content

openmoat_core/ir/
check.rs

1//! Evaluating the IR on atomic actions: the reference semantics every OS
2//! backend must reproduce, and what the consistency tests compare with the engine.
3
4use super::{Access, Effect, Enforcement};
5use crate::action::AtomicAction;
6use crate::kind::Kind;
7use crate::pattern::{GlobPattern, any_match};
8use crate::policy::PolicyError;
9
10/// An [`Enforcement`] with its globs compiled.
11#[derive(Debug)]
12pub struct Checker {
13    /// The read allowances, every one an `fs.read` [`super::Allowance`].
14    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    /// Compile the IR's globs for [`Checker::check`].
30    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    /// What an OS layer does with `atom`: `None` for a decide-only kind.
50    #[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    /// What an OS layer does with `atom` once the [`super::Allowance`]s apply:
70    /// a deny rule still denies, then an allowance allows, then [`Self::check`].
71    #[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}