sandbox_runtime/manager/
mod.rs1pub mod filesystem;
4pub mod network;
5pub mod state;
6
7use std::sync::Arc;
8
9use parking_lot::RwLock;
10
11use crate::config::SandboxRuntimeConfig;
12use crate::error::SandboxError;
13use crate::utils::{current_platform, check_ripgrep, Platform};
14use crate::violation::SandboxViolationStore;
15
16use self::state::ManagerState;
17
18pub use filesystem::{FsReadRestrictionConfig, FsWriteRestrictionConfig};
19
20pub struct SandboxManager {
22 state: Arc<RwLock<ManagerState>>,
23}
24
25impl Default for SandboxManager {
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31impl SandboxManager {
32 pub fn new() -> Self {
34 Self {
35 state: Arc::new(RwLock::new(ManagerState::new())),
36 }
37 }
38
39 pub fn is_supported_platform() -> bool {
41 current_platform().is_some()
42 }
43
44 pub fn check_dependencies(&self, config: Option<&SandboxRuntimeConfig>) -> Result<(), SandboxError> {
46 let platform = current_platform()
47 .ok_or_else(|| SandboxError::UnsupportedPlatform("Unsupported platform".to_string()))?;
48
49 crate::sandbox::check_dependencies(platform)?;
51
52 if platform == Platform::Linux {
54 let rg_config = config.and_then(|c| c.ripgrep.as_ref());
55 if !check_ripgrep(rg_config) {
56 tracing::warn!("ripgrep not found - dangerous file detection will be limited");
57 }
58 }
59
60 Ok(())
61 }
62
63 pub async fn initialize(&self, config: SandboxRuntimeConfig) -> Result<(), SandboxError> {
65 config.validate()?;
67
68 self.check_dependencies(Some(&config))?;
70
71 let platform = current_platform()
72 .ok_or_else(|| SandboxError::UnsupportedPlatform("Unsupported platform".to_string()))?;
73
74 let (http_proxy, socks_proxy) =
76 network::initialize_proxies(&config.network).await?;
77
78 let http_port = http_proxy.port();
79 let socks_port = socks_proxy.port();
80
81 let mut state = self.state.write();
83 state.http_proxy = Some(http_proxy);
84 state.socks_proxy = Some(socks_proxy);
85 state.http_proxy_port = Some(http_port);
86 state.socks_proxy_port = Some(socks_port);
87
88 #[cfg(target_os = "linux")]
90 {
91 use crate::sandbox::linux::{generate_socket_path, SocatBridge};
92
93 let http_socket_path = generate_socket_path("srt-http");
95 let socks_socket_path = generate_socket_path("srt-socks");
96
97 let http_bridge =
98 SocatBridge::unix_to_tcp(http_socket_path.clone(), "localhost", http_port).await?;
99 let socks_bridge =
100 SocatBridge::unix_to_tcp(socks_socket_path.clone(), "localhost", socks_port)
101 .await?;
102
103 state.http_socket_path = Some(http_socket_path.display().to_string());
104 state.socks_socket_path = Some(socks_socket_path.display().to_string());
105 state.bridges.push(http_bridge);
106 state.bridges.push(socks_bridge);
107 }
108
109 state.config = Some(config);
110 state.initialized = true;
111 state.network_ready = true;
112
113 tracing::info!(
114 "Sandbox manager initialized for {} (HTTP proxy: {}, SOCKS proxy: {})",
115 platform.name(),
116 http_port,
117 socks_port
118 );
119
120 Ok(())
121 }
122
123 pub fn is_initialized(&self) -> bool {
125 self.state.read().initialized
126 }
127
128 pub fn get_config(&self) -> Option<SandboxRuntimeConfig> {
130 self.state.read().config.clone()
131 }
132
133 pub fn update_config(&self, config: SandboxRuntimeConfig) -> Result<(), SandboxError> {
135 config.validate()?;
136 self.state.write().config = Some(config);
137 Ok(())
138 }
139
140 pub fn get_proxy_port(&self) -> Option<u16> {
142 self.state.read().http_proxy_port
143 }
144
145 pub fn get_socks_proxy_port(&self) -> Option<u16> {
147 self.state.read().socks_proxy_port
148 }
149
150 #[cfg(target_os = "linux")]
152 pub fn get_http_socket_path(&self) -> Option<String> {
153 self.state.read().http_socket_path.clone()
154 }
155
156 #[cfg(target_os = "linux")]
158 pub fn get_socks_socket_path(&self) -> Option<String> {
159 self.state.read().socks_socket_path.clone()
160 }
161
162 pub fn is_network_ready(&self) -> bool {
164 self.state.read().network_ready
165 }
166
167 pub async fn wait_for_network_initialization(&self) -> bool {
169 self.is_network_ready()
171 }
172
173 pub fn get_fs_read_config(&self) -> FsReadRestrictionConfig {
175 let state = self.state.read();
176 if let Some(ref config) = state.config {
177 filesystem::process_fs_config(&config.filesystem).0
178 } else {
179 FsReadRestrictionConfig::default()
180 }
181 }
182
183 pub fn get_fs_write_config(&self) -> FsWriteRestrictionConfig {
185 let state = self.state.read();
186 if let Some(ref config) = state.config {
187 filesystem::process_fs_config(&config.filesystem).1
188 } else {
189 FsWriteRestrictionConfig::default()
190 }
191 }
192
193 pub fn get_linux_glob_pattern_warnings(&self) -> Vec<String> {
195 #[cfg(target_os = "linux")]
196 {
197 let state = self.state.read();
198 if let Some(ref config) = state.config {
199 let mut warnings = Vec::new();
200 for path in &config.filesystem.allow_write {
201 if crate::utils::contains_glob_chars(path) {
202 warnings.push(format!(
203 "Glob pattern '{}' is not supported on Linux",
204 path
205 ));
206 }
207 }
208 for path in &config.filesystem.deny_write {
209 if crate::utils::contains_glob_chars(path) {
210 warnings.push(format!(
211 "Glob pattern '{}' is not supported on Linux",
212 path
213 ));
214 }
215 }
216 return warnings;
217 }
218 }
219 Vec::new()
220 }
221
222 pub fn get_violation_store(&self) -> Arc<SandboxViolationStore> {
224 self.state.read().violation_store.clone()
225 }
226
227 pub async fn wrap_with_sandbox(
229 &self,
230 command: &str,
231 shell: Option<&str>,
232 custom_config: Option<SandboxRuntimeConfig>,
233 ) -> Result<String, SandboxError> {
234 let (config, http_port, socks_port) = {
236 let state = self.state.read();
237
238 if !state.initialized {
239 return Err(SandboxError::ExecutionFailed(
240 "Sandbox manager not initialized".to_string(),
241 ));
242 }
243
244 let config = custom_config
245 .or_else(|| state.config.clone())
246 .ok_or_else(|| SandboxError::ExecutionFailed("No configuration available".to_string()))?;
247
248 (config, state.http_proxy_port, state.socks_proxy_port)
249 };
250
251 let _platform = current_platform()
252 .ok_or_else(|| SandboxError::UnsupportedPlatform("Unsupported platform".to_string()))?;
253
254 #[cfg(target_os = "macos")]
256 {
257 let (wrapped, _log_tag) = crate::sandbox::macos::wrap_command(
258 command,
259 &config,
260 http_port,
261 socks_port,
262 shell,
263 true, )?;
265 Ok(wrapped)
266 }
267
268 #[cfg(target_os = "linux")]
269 {
270 let (http_socket, socks_socket) = {
271 let state = self.state.read();
272 (state.http_socket_path.clone(), state.socks_socket_path.clone())
273 };
274
275 let cwd = std::env::current_dir()?;
276 let (wrapped, warnings) = crate::sandbox::linux::generate_bwrap_command(
277 command,
278 &config,
279 &cwd,
280 http_socket.as_deref(),
281 socks_socket.as_deref(),
282 http_port.unwrap_or(3128),
283 socks_port.unwrap_or(1080),
284 shell,
285 )?;
286
287 for warning in warnings {
288 tracing::warn!("{}", warning);
289 }
290
291 Ok(wrapped)
292 }
293
294 #[cfg(not(any(target_os = "macos", target_os = "linux")))]
295 {
296 Err(SandboxError::UnsupportedPlatform(
297 "Platform not supported".to_string(),
298 ))
299 }
300 }
301
302 pub fn annotate_stderr_with_sandbox_failures(&self, command: &str, stderr: &str) -> String {
304 let store = self.get_violation_store();
305 let violations = store.get_violations_for_command(command);
306
307 if violations.is_empty() {
308 return stderr.to_string();
309 }
310
311 let mut annotated = stderr.to_string();
312 annotated.push_str("\n\n--- Sandbox Violations ---\n");
313 for violation in violations {
314 annotated.push_str(&format!(" {}\n", violation.line));
315 }
316
317 annotated
318 }
319
320 pub async fn reset(&self) {
322 #[cfg(target_os = "macos")]
324 {
325 crate::sandbox::macos::cleanup_temp_profiles();
326 }
327
328 let mut state = self.state.write();
329 if let Some(ref mut proxy) = state.http_proxy {
334 proxy.stop();
335 }
336 if let Some(ref mut proxy) = state.socks_proxy {
337 proxy.stop();
338 }
339
340 #[cfg(target_os = "linux")]
342 {
343 state.bridges.clear();
345 state.http_socket_path = None;
346 state.socks_socket_path = None;
347 }
348
349 state.http_proxy = None;
351 state.socks_proxy = None;
352 state.http_proxy_port = None;
353 state.socks_proxy_port = None;
354 state.config = None;
355 state.initialized = false;
356 state.network_ready = false;
357
358 tracing::info!("Sandbox manager reset");
359 }
360}
361
362impl Drop for SandboxManager {
363 fn drop(&mut self) {
364 }
366}