vtcode_core/sandboxing/
debug.rs1use std::path::Path;
7use std::process::Stdio;
8
9use anyhow::{Context, Result};
10use tokio::process::Command;
11
12use super::{CommandSpec, SandboxManager, SandboxPolicy, SandboxType};
13
14#[derive(Debug)]
16pub struct SandboxDebugResult {
17 pub success: bool,
19 pub exit_code: Option<i32>,
21 pub stdout: String,
23 pub stderr: String,
25 pub sandbox_type: SandboxType,
27 pub sandbox_active: bool,
29}
30
31impl SandboxDebugResult {
32 pub fn unavailable(sandbox_type: SandboxType) -> Self {
34 Self {
35 success: false,
36 exit_code: None,
37 stdout: String::new(),
38 stderr: format!(
39 "Sandbox type {:?} is not available on this platform",
40 sandbox_type
41 ),
42 sandbox_type,
43 sandbox_active: false,
44 }
45 }
46}
47
48pub async fn debug_sandbox(
52 sandbox_type: SandboxType,
53 policy: &SandboxPolicy,
54 command: &[String],
55 cwd: &Path,
56 sandbox_executable: Option<&Path>,
57) -> Result<SandboxDebugResult> {
58 if !sandbox_type.is_available() {
59 return Ok(SandboxDebugResult::unavailable(sandbox_type));
60 }
61
62 if command.is_empty() {
63 anyhow::bail!("Command cannot be empty");
64 }
65
66 let spec = CommandSpec::new(&command[0])
67 .with_args(command[1..].to_vec())
68 .with_cwd(cwd);
69
70 let manager = SandboxManager::new();
71 let exec_env = manager
72 .transform(spec, policy, cwd, sandbox_executable)
73 .context("Failed to transform command for sandbox")?;
74
75 let mut cmd = Command::new(&exec_env.program);
76 cmd.args(&exec_env.args)
77 .current_dir(&exec_env.cwd)
78 .envs(&exec_env.env)
79 .stdout(Stdio::piped())
80 .stderr(Stdio::piped());
81
82 let output = cmd
83 .output()
84 .await
85 .context("Failed to execute sandboxed command")?;
86
87 Ok(SandboxDebugResult {
88 success: output.status.success(),
89 exit_code: output.status.code(),
90 stdout: String::from_utf8_lossy(&output.stdout).to_string(),
91 stderr: String::from_utf8_lossy(&output.stderr).to_string(),
92 sandbox_type: exec_env.sandbox_type,
93 sandbox_active: exec_env.sandbox_active,
94 })
95}
96
97pub async fn test_path_writable(
99 policy: &SandboxPolicy,
100 test_path: &Path,
101 cwd: &Path,
102 sandbox_executable: Option<&Path>,
103) -> Result<bool> {
104 let test_file = test_path.join(".vtcode_sandbox_test");
105 let test_command = vec![
106 "sh".to_string(),
107 "-c".to_string(),
108 format!(
109 "touch '{}' && rm -f '{}'",
110 test_file.display(),
111 test_file.display()
112 ),
113 ];
114
115 let result = debug_sandbox(
116 SandboxType::platform_default(),
117 policy,
118 &test_command,
119 cwd,
120 sandbox_executable,
121 )
122 .await?;
123
124 Ok(result.success)
125}
126
127pub async fn test_network_blocked(
129 policy: &SandboxPolicy,
130 cwd: &Path,
131 sandbox_executable: Option<&Path>,
132) -> Result<bool> {
133 let test_command = vec![
134 "sh".to_string(),
135 "-c".to_string(),
136 "curl -s --connect-timeout 2 https://example.com > /dev/null 2>&1".to_string(),
137 ];
138
139 let result = debug_sandbox(
140 SandboxType::platform_default(),
141 policy,
142 &test_command,
143 cwd,
144 sandbox_executable,
145 )
146 .await?;
147
148 Ok(!result.success)
149}
150
151pub fn sandbox_capabilities_summary() -> String {
153 let mut summary = String::new();
154
155 summary.push_str("VT Code Sandbox Capabilities\n");
156 summary.push_str("=============================\n\n");
157
158 summary.push_str(&format!(
159 "Platform default: {:?}\n\n",
160 SandboxType::platform_default()
161 ));
162
163 summary.push_str("Available sandbox types:\n");
164 for sandbox_type in [
165 SandboxType::MacosSeatbelt,
166 SandboxType::LinuxLandlock,
167 SandboxType::WindowsRestrictedToken,
168 ] {
169 let available = if sandbox_type.is_available() {
170 "✓"
171 } else {
172 "✗"
173 };
174 summary.push_str(&format!(" {} {:?}\n", available, sandbox_type));
175 }
176
177 summary.push_str("\nSandbox policies:\n");
178 summary.push_str(
179 " - ReadOnly: Read files, no writes except /dev/null, optional network policy\n",
180 );
181 summary
182 .push_str(" - WorkspaceWrite: Read all, write to workspace, optional network allowlist\n");
183 summary.push_str(" - DangerFullAccess: No restrictions (use with caution)\n");
184
185 summary.push_str("\nSecurity features:\n");
186 summary.push_str(" - Sensitive path blocking (~/.ssh, ~/.aws, etc.)\n");
187 summary.push_str(" - .git directory write protection\n");
188 summary.push_str(" - Environment variable sanitization\n");
189 summary.push_str(" - Seccomp syscall filtering (Linux)\n");
190 summary.push_str(" - Resource limits (memory, PIDs, disk, CPU)\n");
191
192 summary
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub enum DebugSubcommand {
198 Seatbelt,
200 Landlock,
202 Capabilities,
204}
205
206impl DebugSubcommand {
207 pub fn sandbox_type(&self) -> SandboxType {
209 match self {
210 Self::Seatbelt => SandboxType::MacosSeatbelt,
211 Self::Landlock => SandboxType::LinuxLandlock,
212 Self::Capabilities => SandboxType::platform_default(),
213 }
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn test_capabilities_summary() {
223 let summary = sandbox_capabilities_summary();
224 assert!(summary.contains("VT Code Sandbox Capabilities"));
225 assert!(summary.contains("Platform default"));
226 assert!(summary.contains("ReadOnly"));
227 assert!(summary.contains("WorkspaceWrite"));
228 }
229
230 #[test]
231 fn test_debug_subcommand() {
232 assert_eq!(
233 DebugSubcommand::Seatbelt.sandbox_type(),
234 SandboxType::MacosSeatbelt
235 );
236 assert_eq!(
237 DebugSubcommand::Landlock.sandbox_type(),
238 SandboxType::LinuxLandlock
239 );
240 }
241
242 #[tokio::test]
243 async fn test_debug_sandbox_unavailable() {
244 let result = SandboxDebugResult::unavailable(SandboxType::LinuxLandlock);
245 assert!(!result.success);
246 assert!(!result.sandbox_active);
247 assert!(result.stderr.contains("not available"));
248 }
249}