1use std::collections::HashSet;
7use std::path::PathBuf;
8
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14pub enum Permission {
15 Read,
17
18 Write,
20
21 Execute,
23
24 Network,
26
27 Environment,
29
30 Clipboard,
32
33 Ui,
35
36 Notify,
38}
39
40#[derive(Debug, Clone)]
42pub struct SandboxConfig {
43 pub allowed_read_paths: Vec<PathBuf>,
45
46 pub allowed_write_paths: Vec<PathBuf>,
48
49 pub allowed_commands: HashSet<String>,
51
52 pub allow_network: bool,
54
55 pub allow_env: bool,
57
58 pub timeout_ms: u64,
60
61 pub max_memory: usize,
63
64 pub max_read_size: usize,
66
67 pub permissions: HashSet<Permission>,
69}
70
71impl Default for SandboxConfig {
72 fn default() -> Self {
73 let mut permissions = HashSet::new();
74 permissions.insert(Permission::Read);
75 permissions.insert(Permission::Notify);
76
77 Self {
78 allowed_read_paths: vec![],
79 allowed_write_paths: vec![],
80 allowed_commands: HashSet::new(),
81 allow_network: false,
82 allow_env: false,
83 timeout_ms: 5000,
84 max_memory: 256 * 1024 * 1024, max_read_size: 10 * 1024 * 1024, permissions,
87 }
88 }
89}
90
91impl SandboxConfig {
92 pub fn minimal() -> Self {
94 Self {
95 allowed_read_paths: vec![],
96 allowed_write_paths: vec![],
97 allowed_commands: HashSet::new(),
98 allow_network: false,
99 allow_env: false,
100 timeout_ms: 1000,
101 max_memory: 64 * 1024 * 1024,
102 max_read_size: 1024 * 1024,
103 permissions: HashSet::new(),
104 }
105 }
106
107 pub fn permissive() -> Self {
109 let mut permissions = HashSet::new();
110 permissions.insert(Permission::Read);
111 permissions.insert(Permission::Write);
112 permissions.insert(Permission::Execute);
113 permissions.insert(Permission::Environment);
114 permissions.insert(Permission::Ui);
115 permissions.insert(Permission::Notify);
116
117 Self {
118 allowed_read_paths: vec![PathBuf::from("/")],
119 allowed_write_paths: vec![],
120 allowed_commands: HashSet::from(["*".to_string()]),
121 allow_network: false,
122 allow_env: true,
123 timeout_ms: 30000,
124 max_memory: 1024 * 1024 * 1024,
125 max_read_size: 100 * 1024 * 1024,
126 permissions,
127 }
128 }
129
130 pub fn allow_read(mut self, path: impl Into<PathBuf>) -> Self {
132 self.allowed_read_paths.push(path.into());
133 self.permissions.insert(Permission::Read);
134 self
135 }
136
137 pub fn allow_write(mut self, path: impl Into<PathBuf>) -> Self {
139 self.allowed_write_paths.push(path.into());
140 self.permissions.insert(Permission::Write);
141 self
142 }
143
144 pub fn allow_command(mut self, cmd: impl Into<String>) -> Self {
146 self.allowed_commands.insert(cmd.into());
147 self.permissions.insert(Permission::Execute);
148 self
149 }
150
151 pub fn allow_network(mut self) -> Self {
153 self.allow_network = true;
154 self.permissions.insert(Permission::Network);
155 self
156 }
157
158 pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
160 self.timeout_ms = timeout_ms;
161 self
162 }
163
164 pub fn with_memory_limit(mut self, bytes: usize) -> Self {
166 self.max_memory = bytes;
167 self
168 }
169
170 pub fn grant(mut self, permission: Permission) -> Self {
172 self.permissions.insert(permission);
173 self
174 }
175
176 pub fn has_permission(&self, permission: Permission) -> bool {
178 self.permissions.contains(&permission)
179 }
180
181 pub fn can_read(&self, path: &PathBuf) -> bool {
183 if !self.has_permission(Permission::Read) {
184 return false;
185 }
186 if self.allowed_read_paths.is_empty() {
187 return true; }
189 self.allowed_read_paths
190 .iter()
191 .any(|allowed| path.starts_with(allowed))
192 }
193
194 pub fn can_write(&self, path: &PathBuf) -> bool {
196 if !self.has_permission(Permission::Write) {
197 return false;
198 }
199 self.allowed_write_paths
200 .iter()
201 .any(|allowed| path.starts_with(allowed))
202 }
203
204 pub fn can_execute(&self, command: &str) -> bool {
206 if !self.has_permission(Permission::Execute) {
207 return false;
208 }
209 self.allowed_commands.contains("*") || self.allowed_commands.contains(command)
210 }
211}
212
213#[derive(Debug, Clone)]
215#[allow(dead_code)]
216pub struct SandboxViolation {
217 pub kind: ViolationKind,
219
220 pub description: String,
222
223 pub path: Option<PathBuf>,
225
226 pub command: Option<String>,
228}
229
230#[derive(Debug, Clone, Copy, PartialEq, Eq)]
232#[allow(dead_code)]
233pub enum ViolationKind {
234 ReadDenied,
236
237 WriteDenied,
239
240 ExecuteDenied,
242
243 NetworkDenied,
245
246 EnvDenied,
248
249 MemoryExceeded,
251
252 TimeoutExceeded,
254
255 FileTooLarge,
257}
258
259impl std::fmt::Display for SandboxViolation {
260 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
261 match self.kind {
262 ViolationKind::ReadDenied => {
263 write!(f, "Read denied: {}", self.description)
264 }
265 ViolationKind::WriteDenied => {
266 write!(f, "Write denied: {}", self.description)
267 }
268 ViolationKind::ExecuteDenied => {
269 write!(f, "Execute denied: {}", self.description)
270 }
271 ViolationKind::NetworkDenied => {
272 write!(f, "Network access denied")
273 }
274 ViolationKind::EnvDenied => {
275 write!(f, "Environment access denied")
276 }
277 ViolationKind::MemoryExceeded => {
278 write!(f, "Memory limit exceeded")
279 }
280 ViolationKind::TimeoutExceeded => {
281 write!(f, "Execution timeout exceeded")
282 }
283 ViolationKind::FileTooLarge => {
284 write!(f, "File too large: {}", self.description)
285 }
286 }
287 }
288}
289
290impl std::error::Error for SandboxViolation {}