driven/security/
sandbox.rs1use super::{Capability, CapabilityManifest};
6use crate::Result;
7use std::collections::HashSet;
8use std::path::{Path, PathBuf};
9
10#[derive(Debug, Clone)]
12pub struct SandboxConfig {
13 pub read_paths: HashSet<PathBuf>,
15 pub write_paths: HashSet<PathBuf>,
17 pub env_vars: HashSet<String>,
19 pub max_memory: usize,
21 pub max_time_ms: u64,
23 pub allow_network: bool,
25 pub allow_spawn: bool,
27}
28
29impl SandboxConfig {
30 pub fn restricted() -> Self {
32 Self {
33 read_paths: HashSet::new(),
34 write_paths: HashSet::new(),
35 env_vars: HashSet::new(),
36 max_memory: 64 * 1024 * 1024, max_time_ms: 5000, allow_network: false,
39 allow_spawn: false,
40 }
41 }
42
43 pub fn permissive() -> Self {
45 Self {
46 read_paths: HashSet::new(), write_paths: HashSet::new(),
48 env_vars: HashSet::new(),
49 max_memory: 512 * 1024 * 1024, max_time_ms: 30000, allow_network: true,
52 allow_spawn: true,
53 }
54 }
55
56 pub fn allow_read(mut self, path: impl Into<PathBuf>) -> Self {
58 self.read_paths.insert(path.into());
59 self
60 }
61
62 pub fn allow_write(mut self, path: impl Into<PathBuf>) -> Self {
64 self.write_paths.insert(path.into());
65 self
66 }
67
68 pub fn allow_env(mut self, var: impl Into<String>) -> Self {
70 self.env_vars.insert(var.into());
71 self
72 }
73
74 pub fn with_memory_limit(mut self, bytes: usize) -> Self {
76 self.max_memory = bytes;
77 self
78 }
79
80 pub fn with_time_limit(mut self, ms: u64) -> Self {
82 self.max_time_ms = ms;
83 self
84 }
85
86 pub fn to_manifest(&self) -> CapabilityManifest {
88 let mut manifest = CapabilityManifest::new();
89
90 if !self.read_paths.is_empty() {
91 manifest.require(Capability::FileRead);
92 }
93
94 if !self.write_paths.is_empty() {
95 manifest.require(Capability::FileWrite);
96 }
97
98 if !self.env_vars.is_empty() {
99 manifest.require(Capability::Environment);
100 }
101
102 if self.allow_network {
103 manifest.require(Capability::Network);
104 } else {
105 manifest.deny(Capability::Network);
106 }
107
108 if self.allow_spawn {
109 manifest.require(Capability::Process);
110 } else {
111 manifest.deny(Capability::Process);
112 }
113
114 manifest
115 }
116}
117
118impl Default for SandboxConfig {
119 fn default() -> Self {
120 Self::restricted()
121 }
122}
123
124#[derive(Debug)]
126pub struct Sandbox {
127 config: SandboxConfig,
129 started: Option<std::time::Instant>,
131 memory_used: usize,
133 violations: Vec<SandboxViolation>,
135}
136
137#[derive(Debug, Clone)]
139pub struct SandboxViolation {
140 pub kind: ViolationKind,
142 pub message: String,
144 pub timestamp: std::time::Instant,
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150pub enum ViolationKind {
151 UnauthorizedRead,
153 UnauthorizedWrite,
155 MemoryExceeded,
157 TimeExceeded,
159 NetworkDenied,
161 SpawnDenied,
163 EnvDenied,
165}
166
167impl Sandbox {
168 pub fn new(config: SandboxConfig) -> Self {
170 Self {
171 config,
172 started: None,
173 memory_used: 0,
174 violations: Vec::new(),
175 }
176 }
177
178 pub fn start(&mut self) {
180 self.started = Some(std::time::Instant::now());
181 self.memory_used = 0;
182 self.violations.clear();
183 }
184
185 pub fn check_time(&mut self) -> Result<bool> {
187 if let Some(started) = self.started {
188 if started.elapsed().as_millis() as u64 > self.config.max_time_ms {
189 self.record_violation(ViolationKind::TimeExceeded, "Time limit exceeded");
190 return Ok(false);
191 }
192 }
193 Ok(true)
194 }
195
196 pub fn check_memory(&mut self, bytes: usize) -> Result<bool> {
198 if self.memory_used + bytes > self.config.max_memory {
199 self.record_violation(ViolationKind::MemoryExceeded, "Memory limit exceeded");
200 return Ok(false);
201 }
202 self.memory_used += bytes;
203 Ok(true)
204 }
205
206 pub fn check_read(&mut self, path: &Path) -> bool {
208 if self.config.read_paths.is_empty() {
210 self.record_violation(
211 ViolationKind::UnauthorizedRead,
212 &format!("Read denied: {}", path.display()),
213 );
214 return false;
215 }
216
217 let allowed = self
219 .config
220 .read_paths
221 .iter()
222 .any(|allowed| path.starts_with(allowed) || path == allowed);
223
224 if !allowed {
225 self.record_violation(
226 ViolationKind::UnauthorizedRead,
227 &format!("Read denied: {}", path.display()),
228 );
229 }
230
231 allowed
232 }
233
234 pub fn check_write(&mut self, path: &Path) -> bool {
236 if self.config.write_paths.is_empty() {
237 self.record_violation(
238 ViolationKind::UnauthorizedWrite,
239 &format!("Write denied: {}", path.display()),
240 );
241 return false;
242 }
243
244 let allowed = self
245 .config
246 .write_paths
247 .iter()
248 .any(|allowed| path.starts_with(allowed) || path == allowed);
249
250 if !allowed {
251 self.record_violation(
252 ViolationKind::UnauthorizedWrite,
253 &format!("Write denied: {}", path.display()),
254 );
255 }
256
257 allowed
258 }
259
260 pub fn check_env(&mut self, var: &str) -> bool {
262 if self.config.env_vars.is_empty() {
263 self.record_violation(
264 ViolationKind::EnvDenied,
265 &format!("Env access denied: {}", var),
266 );
267 return false;
268 }
269
270 let allowed = self.config.env_vars.contains(var);
271
272 if !allowed {
273 self.record_violation(
274 ViolationKind::EnvDenied,
275 &format!("Env access denied: {}", var),
276 );
277 }
278
279 allowed
280 }
281
282 pub fn check_network(&mut self) -> bool {
284 if !self.config.allow_network {
285 self.record_violation(ViolationKind::NetworkDenied, "Network access denied");
286 return false;
287 }
288 true
289 }
290
291 pub fn check_spawn(&mut self) -> bool {
293 if !self.config.allow_spawn {
294 self.record_violation(ViolationKind::SpawnDenied, "Process spawn denied");
295 return false;
296 }
297 true
298 }
299
300 fn record_violation(&mut self, kind: ViolationKind, message: &str) {
302 self.violations.push(SandboxViolation {
303 kind,
304 message: message.to_string(),
305 timestamp: std::time::Instant::now(),
306 });
307 }
308
309 pub fn violations(&self) -> &[SandboxViolation] {
311 &self.violations
312 }
313
314 pub fn has_violations(&self) -> bool {
316 !self.violations.is_empty()
317 }
318
319 pub fn memory_used(&self) -> usize {
321 self.memory_used
322 }
323
324 pub fn elapsed(&self) -> Option<std::time::Duration> {
326 self.started.map(|s| s.elapsed())
327 }
328
329 pub fn reset(&mut self) {
331 self.started = None;
332 self.memory_used = 0;
333 self.violations.clear();
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 #[test]
342 fn test_sandbox_config() {
343 let config = SandboxConfig::restricted()
344 .allow_read("/home/user")
345 .allow_write("/tmp")
346 .with_memory_limit(128 * 1024 * 1024);
347
348 assert!(config.read_paths.contains(Path::new("/home/user")));
349 assert!(config.write_paths.contains(Path::new("/tmp")));
350 assert_eq!(config.max_memory, 128 * 1024 * 1024);
351 }
352
353 #[test]
354 fn test_sandbox_file_access() {
355 let config = SandboxConfig::restricted().allow_read("/home/user/project");
356
357 let mut sandbox = Sandbox::new(config);
358 sandbox.start();
359
360 assert!(sandbox.check_read(Path::new("/home/user/project/src/main.rs")));
361 assert!(!sandbox.check_read(Path::new("/etc/passwd")));
362 assert!(sandbox.has_violations());
363 }
364
365 #[test]
366 fn test_sandbox_memory() {
367 let config = SandboxConfig::restricted().with_memory_limit(1024);
368
369 let mut sandbox = Sandbox::new(config);
370 sandbox.start();
371
372 assert!(sandbox.check_memory(512).unwrap());
373 assert!(sandbox.check_memory(512).unwrap());
374 assert!(!sandbox.check_memory(1).unwrap()); }
376}