a3s_code_core/permissions/
policy.rs1use serde::{Deserialize, Serialize};
2
3use super::{MatchingRules, PermissionChecker, PermissionDecision, PermissionRule};
4use crate::queue::SessionLane;
5
6fn yolo_lane_rule(lane: SessionLane) -> String {
7 match lane {
8 SessionLane::Control => "lane:control".to_string(),
9 SessionLane::Query => "lane:query".to_string(),
10 SessionLane::Execute => "lane:execute".to_string(),
11 SessionLane::Generate => "lane:generate".to_string(),
12 }
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct PermissionPolicy {
24 #[serde(default)]
26 pub deny: Vec<PermissionRule>,
27
28 #[serde(default)]
30 pub allow: Vec<PermissionRule>,
31
32 #[serde(default)]
34 pub ask: Vec<PermissionRule>,
35
36 #[serde(default = "default_decision")]
38 pub default_decision: PermissionDecision,
39
40 #[serde(default = "default_enabled")]
42 pub enabled: bool,
43}
44
45fn default_decision() -> PermissionDecision {
46 PermissionDecision::Ask
47}
48
49fn default_enabled() -> bool {
50 true
51}
52
53impl Default for PermissionPolicy {
54 fn default() -> Self {
55 Self {
56 deny: Vec::new(),
57 allow: Vec::new(),
58 ask: Vec::new(),
59 default_decision: PermissionDecision::Ask,
60 enabled: true,
61 }
62 }
63}
64
65impl PermissionPolicy {
66 pub fn new() -> Self {
68 Self::default()
69 }
70
71 pub fn strict() -> Self {
73 Self {
74 deny: Vec::new(),
75 allow: Vec::new(),
76 ask: Vec::new(),
77 default_decision: PermissionDecision::Ask,
78 enabled: true,
79 }
80 }
81
82 pub fn deny(mut self, rule: &str) -> Self {
84 self.deny.push(PermissionRule::new(rule));
85 self
86 }
87
88 pub fn allow(mut self, rule: &str) -> Self {
90 self.allow.push(PermissionRule::new(rule));
91 self
92 }
93
94 pub fn allow_yolo_lanes(mut self, lanes: impl IntoIterator<Item = SessionLane>) -> Self {
97 for lane in lanes {
98 self.allow.push(PermissionRule::new(&yolo_lane_rule(lane)));
99 }
100 self
101 }
102
103 pub fn ask(mut self, rule: &str) -> Self {
105 self.ask.push(PermissionRule::new(rule));
106 self
107 }
108
109 pub fn deny_all(mut self, rules: &[&str]) -> Self {
111 for rule in rules {
112 self.deny.push(PermissionRule::new(rule));
113 }
114 self
115 }
116
117 pub fn allow_all(mut self, rules: &[&str]) -> Self {
119 for rule in rules {
120 self.allow.push(PermissionRule::new(rule));
121 }
122 self
123 }
124
125 pub fn ask_all(mut self, rules: &[&str]) -> Self {
127 for rule in rules {
128 self.ask.push(PermissionRule::new(rule));
129 }
130 self
131 }
132
133 pub fn check(&self, tool_name: &str, args: &serde_json::Value) -> PermissionDecision {
141 if !self.enabled {
142 return PermissionDecision::Allow;
143 }
144
145 for rule in &self.deny {
147 if rule.matches(tool_name, args) {
148 return PermissionDecision::Deny;
149 }
150 }
151
152 let lane_rule = yolo_lane_rule(SessionLane::from_tool_name(tool_name));
155 for rule in &self.allow {
156 if rule.rule == lane_rule || rule.matches(tool_name, args) {
157 return PermissionDecision::Allow;
158 }
159 }
160
161 for rule in &self.ask {
163 if rule.matches(tool_name, args) {
164 return PermissionDecision::Ask;
165 }
166 }
167
168 self.default_decision
170 }
171
172 pub fn is_allowed(&self, tool_name: &str, args: &serde_json::Value) -> bool {
174 matches!(self.check(tool_name, args), PermissionDecision::Allow)
175 }
176
177 pub fn is_denied(&self, tool_name: &str, args: &serde_json::Value) -> bool {
179 matches!(self.check(tool_name, args), PermissionDecision::Deny)
180 }
181
182 pub fn requires_confirmation(&self, tool_name: &str, args: &serde_json::Value) -> bool {
184 matches!(self.check(tool_name, args), PermissionDecision::Ask)
185 }
186
187 pub fn get_matching_rules(&self, tool_name: &str, args: &serde_json::Value) -> MatchingRules {
189 let mut result = MatchingRules::default();
190
191 for rule in &self.deny {
192 if rule.matches(tool_name, args) {
193 result.deny.push(rule.rule.clone());
194 }
195 }
196
197 for rule in &self.allow {
198 if rule.matches(tool_name, args) {
199 result.allow.push(rule.rule.clone());
200 }
201 }
202
203 for rule in &self.ask {
204 if rule.matches(tool_name, args) {
205 result.ask.push(rule.rule.clone());
206 }
207 }
208
209 result
210 }
211
212 pub fn declares_tool_access(&self, tool_name: &str) -> bool {
220 self.allow
221 .iter()
222 .chain(&self.ask)
223 .any(|rule| rule.matches_tool(tool_name))
224 }
225}
226
227impl PermissionChecker for PermissionPolicy {
228 fn expose_to_model(&self, tool_name: &str) -> bool {
229 if !self.enabled {
230 return true;
231 }
232
233 if self
237 .deny
238 .iter()
239 .any(|rule| rule.matches_tool(tool_name) && rule.matches_all_args())
240 {
241 return false;
242 }
243
244 if self.default_decision != PermissionDecision::Deny {
245 return true;
246 }
247
248 self.declares_tool_access(tool_name)
253 }
254
255 fn check(&self, tool_name: &str, args: &serde_json::Value) -> PermissionDecision {
256 self.check(tool_name, args)
257 }
258}