agentic_sandbox/
process.rs1use async_trait::async_trait;
7use std::process::Stdio;
8use tokio::process::Command;
9use tracing::{debug, instrument, warn};
10
11use crate::{
12 error::SandboxError,
13 traits::{ExecutionResult, Sandbox},
14};
15
16#[derive(Clone)]
20pub struct ProcessSandbox {
21 timeout_ms: u64,
22 shell: String,
23}
24
25impl ProcessSandbox {
26 #[must_use]
28 pub fn new() -> Self {
29 Self {
30 timeout_ms: 30000,
31 shell: if cfg!(windows) {
32 "cmd".to_string()
33 } else {
34 "sh".to_string()
35 },
36 }
37 }
38
39 #[must_use]
41 pub const fn with_timeout(mut self, timeout_ms: u64) -> Self {
42 self.timeout_ms = timeout_ms;
43 self
44 }
45
46 #[must_use]
48 pub fn with_shell(mut self, shell: impl Into<String>) -> Self {
49 self.shell = shell.into();
50 self
51 }
52}
53
54impl Default for ProcessSandbox {
55 fn default() -> Self {
56 Self::new()
57 }
58}
59
60#[async_trait]
61impl Sandbox for ProcessSandbox {
62 #[instrument(skip(self, code), fields(sandbox = "process"))]
63 async fn execute(&self, code: &str) -> Result<ExecutionResult, SandboxError> {
64 warn!("ProcessSandbox provides NO security isolation!");
65 debug!("Executing code: {}...", &code[..code.len().min(100)]);
66
67 let start = std::time::Instant::now();
68
69 let shell_arg = if cfg!(windows) { "/C" } else { "-c" };
70
71 let output = tokio::time::timeout(
72 std::time::Duration::from_millis(self.timeout_ms),
73 Command::new(&self.shell)
74 .arg(shell_arg)
75 .arg(code)
76 .stdout(Stdio::piped())
77 .stderr(Stdio::piped())
78 .output(),
79 )
80 .await
81 .map_err(|_| SandboxError::Timeout)?
82 .map_err(SandboxError::IoError)?;
83
84 #[allow(clippy::cast_possible_truncation)]
85 let execution_time_ms = start.elapsed().as_millis() as u64;
86
87 Ok(ExecutionResult {
88 exit_code: output.status.code().unwrap_or(-1),
89 stdout: String::from_utf8_lossy(&output.stdout).to_string(),
90 stderr: String::from_utf8_lossy(&output.stderr).to_string(),
91 execution_time_ms,
92 artifacts: vec![],
93 })
94 }
95
96 async fn is_ready(&self) -> Result<bool, SandboxError> {
97 Ok(true) }
99
100 async fn stop(&self) -> Result<(), SandboxError> {
101 Ok(()) }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[tokio::test]
110 async fn test_simple_command() {
111 let sandbox = ProcessSandbox::new();
112 let result = sandbox.execute("echo hello").await.unwrap();
113
114 assert!(result.is_success());
115 assert!(result.stdout.contains("hello"));
116 }
117
118 #[tokio::test]
119 async fn test_exit_code() {
120 let sandbox = ProcessSandbox::new();
121 let result = sandbox.execute("exit 42").await.unwrap();
122
123 assert_eq!(result.exit_code, 42);
124 }
125}