submilli_engine/runtime/
security.rs1use std::sync::Arc;
7
8use super::decision::{CallSite, DecisionExplanation, DecisionRecorder};
9
10#[non_exhaustive]
11pub enum CheckOutcome {
12 Allow { rule: Option<usize> },
13 Deny { reason: String, rule: Option<usize> },
14}
15
16#[derive(Clone, Copy)]
21#[non_exhaustive]
22pub struct AuditDecision<'a> {
23 pub caller: &'a str,
24 pub capability: &'a str,
25 pub context: &'a serde_json::Value,
26 pub allowed: bool,
27 pub source: &'a str,
28 pub rule: Option<usize>,
29 pub reason: Option<&'a str>,
30 pub explanation: Option<&'a DecisionExplanation>,
32 pub site: CallSite,
34}
35
36impl<'a> AuditDecision<'a> {
37 pub fn new(
38 caller: &'a str,
39 capability: &'a str,
40 context: &'a serde_json::Value,
41 allowed: bool,
42 source: &'a str,
43 rule: Option<usize>,
44 reason: Option<&'a str>,
45 ) -> Self {
46 Self {
47 caller,
48 capability,
49 context,
50 allowed,
51 source,
52 rule,
53 reason,
54 explanation: None,
55 site: CallSite::default(),
56 }
57 }
58
59 pub fn with_explanation(mut self, explanation: Option<&'a DecisionExplanation>) -> Self {
60 self.explanation = explanation;
61 self
62 }
63
64 pub fn with_site(mut self, site: CallSite) -> Self {
65 self.site = site;
66 self
67 }
68}
69
70pub trait SecurityCheck: Send + Sync {
71 fn audit(&self, _decision: AuditDecision<'_>) {}
73 fn audit_context<'a>(
75 &self,
76 _capability: &str,
77 context: &'a serde_json::Value,
78 _cwd: &str,
79 ) -> std::borrow::Cow<'a, serde_json::Value> {
80 std::borrow::Cow::Borrowed(context)
81 }
82 fn check_with_cwd(
83 &self,
84 caller: &str,
85 capability: &str,
86 context: &serde_json::Value,
87 _cwd: &str,
88 ) -> CheckOutcome {
89 self.check(caller, capability, context)
90 }
91 fn check(&self, caller: &str, capability: &str, context: &serde_json::Value) -> CheckOutcome;
92 fn recorder(&self) -> Option<&dyn DecisionRecorder> {
95 None
96 }
97 fn explain(
101 &self,
102 _caller: &str,
103 _capability: &str,
104 _context: &serde_json::Value,
105 _cwd: &str,
106 ) -> Option<DecisionExplanation> {
107 None
108 }
109}
110
111pub struct AllowAllCheck;
114
115impl SecurityCheck for AllowAllCheck {
116 fn check(&self, caller: &str, capability: &str, context: &serde_json::Value) -> CheckOutcome {
117 use std::io::Write;
118 let _ = writeln!(
119 std::io::stderr().lock(),
120 "[security] caller={caller} capability={capability} context={context}"
121 );
122 CheckOutcome::Allow { rule: None }
123 }
124}
125
126pub fn default_check() -> Arc<dyn SecurityCheck> {
127 Arc::new(AllowAllCheck)
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn allow_all_returns_allow() {
136 let check = AllowAllCheck;
137 let outcome = check.check("main", "test.com/op", &serde_json::json!({"foo": 1}));
138 assert!(matches!(outcome, CheckOutcome::Allow { .. }));
139 }
140
141 #[test]
142 fn default_check_is_allow_all() {
143 let check = default_check();
144 let outcome = check.check("main", "x", &serde_json::Value::Null);
145 assert!(matches!(outcome, CheckOutcome::Allow { .. }));
146 }
147}