1#![allow(dead_code)]
4
5#[cfg(target_os = "macos")]
6pub mod seatbelt;
7
8#[cfg(target_os = "macos")]
9pub use seatbelt::MACOS_PATH_TO_SEATBELT_EXECUTABLE;
10
11use std::collections::HashMap;
12#[allow(unused_imports)]
13use std::ffi::OsString;
14use std::path::{Path, PathBuf};
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
18pub enum SandboxType {
19 #[default]
21 None,
22 MacosSeatbelt,
24 LinuxSeccomp,
26 WindowsRestrictedToken,
28 FreeBSDCapsicum,
30 OpenBSDPledge,
32}
33
34impl SandboxType {
35 pub fn as_metric_tag(self) -> &'static str {
36 match self {
37 SandboxType::None => "none",
38 SandboxType::MacosSeatbelt => "seatbelt",
39 SandboxType::LinuxSeccomp => "seccomp",
40 SandboxType::WindowsRestrictedToken => "windows_sandbox",
41 SandboxType::FreeBSDCapsicum => "capsicum",
42 SandboxType::OpenBSDPledge => "pledge",
43 }
44 }
45
46 pub fn name(&self) -> &'static str {
48 match self {
49 SandboxType::None => "none",
50 SandboxType::MacosSeatbelt => "seatbelt",
51 SandboxType::LinuxSeccomp => "linux-seccomp",
52 SandboxType::WindowsRestrictedToken => "windows-restricted-token",
53 SandboxType::FreeBSDCapsicum => "capsicum",
54 SandboxType::OpenBSDPledge => "pledge",
55 }
56 }
57}
58
59#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
61pub enum SandboxablePreference {
62 #[default]
64 Auto,
65 Require,
67 Forbid,
69}
70
71#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
73pub enum NetworkSandboxPolicy {
74 #[default]
76 FullAccess,
77 NoAccess,
79 Localhost,
81 Proxy,
83}
84
85#[derive(Clone, Debug, PartialEq, Eq, Default)]
87pub enum FileSystemSandboxPolicy {
88 #[default]
90 FullAccess,
91 ReadOnly,
93 WorkspaceWrite {
95 writable_roots: Vec<PathBuf>,
97 },
98 External,
100}
101
102#[derive(Clone, Debug, PartialEq, Eq)]
104pub enum SandboxPolicy {
105 DangerFullAccess,
107 ReadOnly {
109 file_system: FileSystemSandboxPolicy,
110 network_access: NetworkSandboxPolicy,
111 },
112 ExternalSandbox {
114 network_access: NetworkSandboxPolicy,
115 },
116 WorkspaceWrite {
118 writable_roots: Vec<PathBuf>,
119 network_access: NetworkSandboxPolicy,
120 },
121}
122
123impl Default for SandboxPolicy {
124 fn default() -> Self {
126 SandboxPolicy::ReadOnly {
127 file_system: FileSystemSandboxPolicy::ReadOnly,
128 network_access: NetworkSandboxPolicy::NoAccess,
129 }
130 }
131}
132
133impl SandboxPolicy {
134 pub fn network_policy(&self) -> NetworkSandboxPolicy {
136 match self {
137 SandboxPolicy::DangerFullAccess => NetworkSandboxPolicy::FullAccess,
138 SandboxPolicy::ReadOnly { network_access, .. } => *network_access,
139 SandboxPolicy::ExternalSandbox { network_access } => *network_access,
140 SandboxPolicy::WorkspaceWrite { network_access, .. } => *network_access,
141 }
142 }
143
144 pub fn filesystem_policy(&self) -> FileSystemSandboxPolicy {
146 match self {
147 SandboxPolicy::DangerFullAccess => FileSystemSandboxPolicy::FullAccess,
148 SandboxPolicy::ReadOnly { file_system, .. } => file_system.clone(),
149 SandboxPolicy::ExternalSandbox { .. } => FileSystemSandboxPolicy::External,
150 SandboxPolicy::WorkspaceWrite { writable_roots, .. } => {
151 if writable_roots.is_empty() {
153 FileSystemSandboxPolicy::ReadOnly
154 } else {
155 FileSystemSandboxPolicy::WorkspaceWrite {
156 writable_roots: writable_roots.clone(),
157 }
158 }
159 }
160 }
161 }
162
163 pub fn contains_path_traversal(path: &Path) -> bool {
165 let path_str = path.to_string_lossy();
166
167 if path_str.contains("..") {
169 return true;
170 }
171
172 if path_str.contains("/.") || path_str.contains("./") {
174 return true;
175 }
176
177 false
178 }
179
180 pub fn is_safe(&self) -> bool {
183 match self {
184 SandboxPolicy::DangerFullAccess => false,
186 SandboxPolicy::ReadOnly { .. } => true,
188 SandboxPolicy::ExternalSandbox { .. } => false,
190 SandboxPolicy::WorkspaceWrite { writable_roots, .. } => {
192 if writable_roots.is_empty() {
193 return false;
194 }
195 !writable_roots
197 .iter()
198 .any(|p| SandboxPolicy::contains_path_traversal(p))
199 }
200 }
201 }
202}
203
204pub trait SandboxPolicyExt {
206 fn is_safe(&self) -> bool;
207}
208
209impl SandboxPolicyExt for SandboxPolicy {
210 fn is_safe(&self) -> bool {
211 SandboxPolicy::is_safe(self)
212 }
213}
214#[derive(Debug)]
216pub struct SandboxCommand {
217 pub program: OsString,
218 pub args: Vec<String>,
219 pub cwd: PathBuf,
220 pub env: HashMap<String, String>,
221}
222
223#[derive(Debug)]
225pub struct SandboxExecRequest {
226 pub command: Vec<String>,
227 pub cwd: PathBuf,
228 pub env: HashMap<String, String>,
229 pub sandbox: SandboxType,
230 pub sandbox_policy: SandboxPolicy,
231 pub file_system_policy: FileSystemSandboxPolicy,
232 pub network_policy: NetworkSandboxPolicy,
233 pub arg0: Option<String>,
234}
235
236#[derive(Debug)]
238pub enum SandboxTransformError {
239 MissingLinuxSandboxExecutable,
240 #[cfg(not(target_os = "macos"))]
241 SeatbeltUnavailable,
242 PlatformNotSupported,
243 UnsafePolicy(String),
245}
246
247impl std::fmt::Display for SandboxTransformError {
248 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
249 match self {
250 Self::MissingLinuxSandboxExecutable => {
251 write!(f, "missing linux-sandbox executable path")
252 }
253 #[cfg(not(target_os = "macos"))]
254 Self::SeatbeltUnavailable => write!(f, "seatbelt sandbox is only available on macOS"),
255 Self::PlatformNotSupported => write!(f, "sandbox is not supported on this platform"),
256 Self::UnsafePolicy(reason) => write!(f, "unsafe policy: {}", reason),
257 }
258 }
259}
260
261impl std::error::Error for SandboxTransformError {}
262
263pub fn get_platform_sandbox(windows_sandbox_enabled: bool) -> Option<SandboxType> {
265 if cfg!(target_os = "macos") {
266 Some(SandboxType::MacosSeatbelt)
267 } else if cfg!(target_os = "linux") {
268 Some(SandboxType::LinuxSeccomp)
269 } else if cfg!(target_os = "freebsd") {
270 Some(SandboxType::FreeBSDCapsicum)
271 } else if cfg!(target_os = "openbsd") {
272 Some(SandboxType::OpenBSDPledge)
273 } else if cfg!(target_os = "windows") {
274 if windows_sandbox_enabled {
275 Some(SandboxType::WindowsRestrictedToken)
276 } else {
277 None
278 }
279 } else {
280 None
281 }
282}
283
284#[derive(Default)]
286pub struct SandboxManager;
287
288impl SandboxManager {
289 pub fn new() -> Self {
290 Self
291 }
292
293 #[allow(unused_variables)]
295 pub fn select_initial(
296 &self,
297 file_system_policy: &FileSystemSandboxPolicy,
298 network_policy: NetworkSandboxPolicy,
299 pref: SandboxablePreference,
300 windows_sandbox_enabled: bool,
301 ) -> SandboxType {
302 match pref {
303 SandboxablePreference::Forbid => SandboxType::None,
304 SandboxablePreference::Require => {
305 get_platform_sandbox(windows_sandbox_enabled).unwrap_or(SandboxType::None)
306 }
307 SandboxablePreference::Auto => {
308 let platform_sandbox = get_platform_sandbox(windows_sandbox_enabled);
309 platform_sandbox.unwrap_or(SandboxType::None)
311 }
312 }
313 }
314
315 pub fn create_exec_request(
317 &self,
318 command: SandboxCommand,
319 policy: SandboxPolicy,
320 ) -> Result<SandboxExecRequest, SandboxTransformError> {
321 if !policy.is_safe() {
323 return Err(SandboxTransformError::UnsafePolicy(
324 "Policy failed safety check: empty writable_roots or path traversal detected"
325 .to_string(),
326 ));
327 }
328
329 let sandbox = self.select_initial(
330 &FileSystemSandboxPolicy::default(),
331 NetworkSandboxPolicy::default(),
332 SandboxablePreference::Auto,
333 false,
334 );
335 self.transform_command(command, policy, sandbox, None)
336 }
337
338 pub fn transform_command(
340 &self,
341 command: SandboxCommand,
342 policy: SandboxPolicy,
343 sandbox: SandboxType,
344 _linux_sandbox_exe: Option<&Path>,
345 ) -> Result<SandboxExecRequest, SandboxTransformError> {
346 let argv: Vec<OsString> = std::iter::once(command.program)
347 .chain(command.args.iter().map(OsString::from))
348 .collect();
349
350 let (argv, arg0_override) = match sandbox {
351 SandboxType::None => (os_argv_to_strings(argv), None),
352 #[cfg(target_os = "macos")]
353 SandboxType::MacosSeatbelt => {
354 let args = crate::sandboxing::seatbelt::create_seatbelt_command_args_for_policies(
355 os_argv_to_strings(argv),
356 &policy.filesystem_policy(),
357 policy.network_policy(),
358 std::path::Path::new("."),
359 false,
360 None,
361 );
362 let mut full_command = vec![MACOS_PATH_TO_SEATBELT_EXECUTABLE.to_string()];
363 full_command.extend(args);
364 (full_command, None)
365 }
366 #[cfg(not(target_os = "macos"))]
367 SandboxType::MacosSeatbelt => return Err(SandboxTransformError::SeatbeltUnavailable),
368 SandboxType::LinuxSeccomp => {
369 let exe = _linux_sandbox_exe
370 .ok_or(SandboxTransformError::MissingLinuxSandboxExecutable)?;
371 let args = create_linux_sandbox_args(&policy, command.cwd.as_path());
372 let mut full_command = vec![exe.to_string_lossy().to_string()];
373 full_command.extend(args);
374 (full_command, Some("linux-sandbox".to_string()))
375 }
376 #[cfg(target_os = "windows")]
377 SandboxType::WindowsRestrictedToken => (os_argv_to_strings(argv), None),
378 #[cfg(not(target_os = "windows"))]
379 SandboxType::WindowsRestrictedToken => (os_argv_to_strings(argv), None),
380 #[cfg(target_os = "freebsd")]
381 SandboxType::FreeBSDCapsicum => (os_argv_to_strings(argv), None),
382 #[cfg(not(target_os = "freebsd"))]
383 SandboxType::FreeBSDCapsicum => (os_argv_to_strings(argv), None),
384 #[cfg(target_os = "openbsd")]
385 SandboxType::OpenBSDPledge => (os_argv_to_strings(argv), None),
386 #[cfg(not(target_os = "openbsd"))]
387 SandboxType::OpenBSDPledge => (os_argv_to_strings(argv), None),
388 };
389
390 Ok(SandboxExecRequest {
391 command: argv,
392 cwd: command.cwd,
393 env: command.env,
394 sandbox,
395 sandbox_policy: policy.clone(),
396 file_system_policy: policy.filesystem_policy(),
397 network_policy: policy.network_policy(),
398 arg0: arg0_override,
399 })
400 }
401}
402
403fn os_argv_to_strings(argv: Vec<OsString>) -> Vec<String> {
404 argv.into_iter()
405 .map(|s| {
406 s.into_string()
407 .unwrap_or_else(|s| s.to_string_lossy().into_owned())
408 })
409 .collect()
410}
411
412fn should_require_platform_sandbox(
413 file_system_policy: &FileSystemSandboxPolicy,
414 network_policy: NetworkSandboxPolicy,
415) -> bool {
416 !matches!(file_system_policy, FileSystemSandboxPolicy::FullAccess)
417 || !matches!(network_policy, NetworkSandboxPolicy::FullAccess)
418}
419
420#[cfg(target_os = "macos")]
421#[allow(dead_code)]
422fn create_seatbelt_command_args(_policy: &SandboxPolicy) -> Vec<String> {
423 vec!["-p".to_string(), "(version 1)".to_string()]
424}
425
426#[cfg(target_os = "linux")]
427fn create_linux_sandbox_args(policy: &SandboxPolicy, cwd: &Path) -> Vec<String> {
428 crate::linux_sandbox::create_linux_sandbox_command_args_for_policies(vec![], cwd, policy, false)
429}
430
431#[cfg(not(target_os = "linux"))]
432#[allow(dead_code)]
433fn create_linux_sandbox_args(_policy: &SandboxPolicy, _cwd: &Path) -> Vec<String> {
434 vec![]
435}
436
437#[cfg(test)]
438#[allow(clippy::assertions_on_constants)]
439mod tests {
440 use super::*;
441
442 #[test]
443 fn test_get_platform_sandbox() {
444 #[cfg(target_os = "windows")]
447 let result = get_platform_sandbox(true);
448 #[cfg(not(target_os = "windows"))]
449 let result = get_platform_sandbox(false);
450
451 #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
452 assert!(result.is_some());
453 #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
454 assert!(result.is_none());
455 }
456
457 #[test]
458 #[cfg(target_os = "macos")]
459 fn test_sandbox_manager_create_request() {
460 let manager = SandboxManager::new();
461 let command = SandboxCommand {
462 program: OsString::from("ls"),
463 args: vec!["-la".to_string()],
464 cwd: PathBuf::from("/tmp"),
465 env: HashMap::new(),
466 };
467
468 let result = manager.create_exec_request(command, SandboxPolicy::default());
469 assert!(result.is_ok());
470 }
471
472 #[test]
473 #[cfg(not(target_os = "macos"))]
474 fn test_sandbox_manager_create_request() {
475 }
479
480 #[test]
485 fn test_default_policy_should_be_secure() {
486 let default_policy = SandboxPolicy::default();
489
490 assert!(
492 !matches!(default_policy, SandboxPolicy::DangerFullAccess),
493 "默认策略不应该是 DangerFullAccess,这是安全漏洞!"
494 );
495 }
496
497 #[test]
498 fn test_workspace_write_rejects_empty_paths() {
499 let empty_policy = SandboxPolicy::WorkspaceWrite {
501 writable_roots: vec![],
502 network_access: NetworkSandboxPolicy::NoAccess,
503 };
504
505 let fs_policy = empty_policy.filesystem_policy();
507 match fs_policy {
508 FileSystemSandboxPolicy::ReadOnly => {
509 }
511 FileSystemSandboxPolicy::WorkspaceWrite { writable_roots } => {
512 assert!(
513 !writable_roots.is_empty(),
514 "空的 writable_roots 应该被拒绝或自动处理"
515 );
516 }
517 _ => {
518 panic!("Unexpected filesystem policy variant");
519 }
520 }
521
522 assert!(
524 !empty_policy.is_safe(),
525 "空 writable_roots 的 WorkspaceWrite 应该是不安全的"
526 );
527
528 let safe_policy = SandboxPolicy::WorkspaceWrite {
530 writable_roots: vec![PathBuf::from("/tmp")],
531 network_access: NetworkSandboxPolicy::NoAccess,
532 };
533 assert!(
534 safe_policy.is_safe(),
535 "有有效路径的 WorkspaceWrite 应该是安全的"
536 );
537 }
538
539 #[test]
540 fn test_workspace_write_rejects_path_traversal() {
541 let traversal_paths = vec![
543 PathBuf::from("/tmp/../etc"),
544 PathBuf::from("/tmp/../../etc"),
545 PathBuf::from("/home/../../../root"),
546 PathBuf::from("/tmp/./secret"),
547 ];
548
549 for path in traversal_paths {
550 let policy = SandboxPolicy::WorkspaceWrite {
551 writable_roots: vec![path.clone()],
552 network_access: NetworkSandboxPolicy::NoAccess,
553 };
554
555 assert!(
557 !policy.is_safe(),
558 "包含路径遍历的路径 {:?} 应该被认为是不安全的",
559 path
560 );
561 }
562 }
563
564 #[test]
565 fn test_workspace_write_accepts_valid_paths() {
566 let valid_paths = vec![
568 PathBuf::from("/tmp"),
569 PathBuf::from("/home/user/workspace"),
570 PathBuf::from("/var/data"),
571 ];
572
573 for path in valid_paths {
574 let policy = SandboxPolicy::WorkspaceWrite {
575 writable_roots: vec![path.clone()],
576 network_access: NetworkSandboxPolicy::NoAccess,
577 };
578
579 assert!(policy.is_safe(), "有效路径 {:?} 应该被认为安全的", path);
580 }
581 }
582
583 #[test]
584 fn test_readonly_policy_structure() {
585 let policy = SandboxPolicy::ReadOnly {
587 file_system: FileSystemSandboxPolicy::ReadOnly,
588 network_access: NetworkSandboxPolicy::NoAccess,
589 };
590
591 match policy {
592 SandboxPolicy::ReadOnly {
593 file_system,
594 network_access,
595 } => {
596 assert!(matches!(file_system, FileSystemSandboxPolicy::ReadOnly));
597 assert!(matches!(network_access, NetworkSandboxPolicy::NoAccess));
598 }
599 _ => panic!("Expected ReadOnly policy"),
600 }
601 }
602
603 #[test]
604 fn test_workspace_policy_validates_paths() {
605 let policy = SandboxPolicy::WorkspaceWrite {
607 writable_roots: vec![PathBuf::from("/tmp"), PathBuf::from("/home/user/workspace")],
608 network_access: NetworkSandboxPolicy::Localhost,
609 };
610
611 match policy {
612 SandboxPolicy::WorkspaceWrite {
613 writable_roots,
614 network_access,
615 } => {
616 assert_eq!(writable_roots.len(), 2);
617 assert!(matches!(network_access, NetworkSandboxPolicy::Localhost));
618 }
619 _ => panic!("Expected WorkspaceWrite policy"),
620 }
621 }
622
623 #[test]
624 fn test_network_policy_variants() {
625 assert!(matches!(
627 NetworkSandboxPolicy::default(),
628 NetworkSandboxPolicy::FullAccess
629 ));
630 assert!(matches!(
631 NetworkSandboxPolicy::NoAccess,
632 NetworkSandboxPolicy::NoAccess
633 ));
634 assert!(matches!(
635 NetworkSandboxPolicy::Localhost,
636 NetworkSandboxPolicy::Localhost
637 ));
638 assert!(matches!(
639 NetworkSandboxPolicy::Proxy,
640 NetworkSandboxPolicy::Proxy
641 ));
642 }
643
644 #[test]
645 fn test_filesystem_policy_variants() {
646 assert!(matches!(
648 FileSystemSandboxPolicy::default(),
649 FileSystemSandboxPolicy::FullAccess
650 ));
651 assert!(matches!(
652 FileSystemSandboxPolicy::ReadOnly,
653 FileSystemSandboxPolicy::ReadOnly
654 ));
655 assert!(matches!(
656 FileSystemSandboxPolicy::External,
657 FileSystemSandboxPolicy::External
658 ));
659
660 let ws = FileSystemSandboxPolicy::WorkspaceWrite {
662 writable_roots: vec![PathBuf::from("/tmp")],
663 };
664 assert!(matches!(ws, FileSystemSandboxPolicy::WorkspaceWrite { .. }));
665 }
666
667 #[test]
668 fn test_sandbox_type_all_variants() {
669 let types = vec![
671 SandboxType::None,
672 SandboxType::MacosSeatbelt,
673 SandboxType::LinuxSeccomp,
674 SandboxType::WindowsRestrictedToken,
675 SandboxType::FreeBSDCapsicum,
676 SandboxType::OpenBSDPledge,
677 ];
678
679 for sandbox_type in types {
680 let name = sandbox_type.name();
681 let tag = sandbox_type.as_metric_tag();
682 assert!(!name.is_empty());
683 assert!(!tag.is_empty());
684 }
685 }
686
687 #[test]
688 fn test_sandbox_command_validation() {
689 let command = SandboxCommand {
691 program: OsString::from("ls"),
692 args: vec!["-la".to_string(), "/tmp".to_string()],
693 cwd: PathBuf::from("/tmp"),
694 env: HashMap::new(),
695 };
696
697 assert!(!command.program.is_empty());
698 assert!(!command.args.is_empty());
699 assert!(command.cwd.exists() || command.cwd.to_string_lossy() == "/tmp");
700 }
701
702 #[test]
703 fn test_sandbox_preference_variants() {
704 assert!(matches!(
706 SandboxablePreference::default(),
707 SandboxablePreference::Auto
708 ));
709 assert!(matches!(
710 SandboxablePreference::Require,
711 SandboxablePreference::Require
712 ));
713 assert!(matches!(
714 SandboxablePreference::Forbid,
715 SandboxablePreference::Forbid
716 ));
717 }
718
719 #[test]
724 fn test_empty_program_name() {
725 let manager = SandboxManager::new();
727 let command = SandboxCommand {
728 program: OsString::from(""),
729 args: vec![],
730 cwd: PathBuf::from("/tmp"),
731 env: HashMap::new(),
732 };
733
734 let result = manager.create_exec_request(command, SandboxPolicy::default());
736 assert!(result.is_ok() || result.is_err());
738 }
739
740 #[test]
741 fn test_very_long_program_name() {
742 let manager = SandboxManager::new();
744 let long_name = "A".repeat(10000);
745 let command = SandboxCommand {
746 program: OsString::from(long_name),
747 args: vec![],
748 cwd: PathBuf::from("/tmp"),
749 env: HashMap::new(),
750 };
751
752 let result = manager.create_exec_request(command, SandboxPolicy::default());
753 assert!(result.is_ok() || result.is_err());
755 }
756
757 #[test]
758 fn test_special_characters_in_args() {
759 let manager = SandboxManager::new();
761 let command = SandboxCommand {
762 program: OsString::from("ls"),
763 args: vec![
764 "-la".to_string(),
765 "/tmp".to_string(),
766 ";rm -rf /".to_string(),
767 "|cat /etc/passwd".to_string(),
768 "`whoami`".to_string(),
769 "$(id)".to_string(),
770 ],
771 cwd: PathBuf::from("/tmp"),
772 env: HashMap::new(),
773 };
774
775 let result = manager.create_exec_request(command, SandboxPolicy::default());
776 assert!(result.is_ok() || result.is_err());
778 }
779
780 #[test]
781 fn test_empty_cwd() {
782 let manager = SandboxManager::new();
784 let command = SandboxCommand {
785 program: OsString::from("ls"),
786 args: vec![],
787 cwd: PathBuf::from(""),
788 env: HashMap::new(),
789 };
790
791 let result = manager.create_exec_request(command, SandboxPolicy::default());
792 assert!(result.is_ok() || result.is_err());
794 }
795
796 #[test]
797 fn test_nonexistent_cwd() {
798 let manager = SandboxManager::new();
800 let command = SandboxCommand {
801 program: OsString::from("ls"),
802 args: vec![],
803 cwd: PathBuf::from("/nonexistent/path/that/does/not/exist"),
804 env: HashMap::new(),
805 };
806
807 let result = manager.create_exec_request(command, SandboxPolicy::default());
808 assert!(result.is_ok() || result.is_err());
810 }
811
812 #[test]
817 fn test_policy_clone() {
818 let policy = SandboxPolicy::ReadOnly {
820 file_system: FileSystemSandboxPolicy::ReadOnly,
821 network_access: NetworkSandboxPolicy::NoAccess,
822 };
823
824 let cloned = policy.clone();
825 assert_eq!(policy, cloned);
826 }
827
828 #[test]
829 fn test_policy_debug_format() {
830 let policy = SandboxPolicy::WorkspaceWrite {
832 writable_roots: vec![PathBuf::from("/tmp")],
833 network_access: NetworkSandboxPolicy::Localhost,
834 };
835
836 let debug_str = format!("{:?}", policy);
837 assert!(!debug_str.is_empty());
838 }
839
840 #[test]
845 fn test_sandbox_policy_network_policy_method() {
846 let policy_full = SandboxPolicy::DangerFullAccess;
848 assert_eq!(
849 policy_full.network_policy(),
850 NetworkSandboxPolicy::FullAccess
851 );
852
853 let policy_readonly = SandboxPolicy::ReadOnly {
854 file_system: FileSystemSandboxPolicy::ReadOnly,
855 network_access: NetworkSandboxPolicy::NoAccess,
856 };
857 assert_eq!(
858 policy_readonly.network_policy(),
859 NetworkSandboxPolicy::NoAccess
860 );
861
862 let policy_external = SandboxPolicy::ExternalSandbox {
863 network_access: NetworkSandboxPolicy::Localhost,
864 };
865 assert_eq!(
866 policy_external.network_policy(),
867 NetworkSandboxPolicy::Localhost
868 );
869
870 let policy_workspace = SandboxPolicy::WorkspaceWrite {
871 writable_roots: vec![PathBuf::from("/tmp")],
872 network_access: NetworkSandboxPolicy::Proxy,
873 };
874 assert_eq!(
875 policy_workspace.network_policy(),
876 NetworkSandboxPolicy::Proxy
877 );
878 }
879
880 #[test]
881 fn test_sandbox_policy_filesystem_policy_method() {
882 let policy_full = SandboxPolicy::DangerFullAccess;
884 assert_eq!(
885 policy_full.filesystem_policy(),
886 FileSystemSandboxPolicy::FullAccess
887 );
888
889 let policy_readonly = SandboxPolicy::ReadOnly {
890 file_system: FileSystemSandboxPolicy::ReadOnly,
891 network_access: NetworkSandboxPolicy::NoAccess,
892 };
893 assert_eq!(
894 policy_readonly.filesystem_policy(),
895 FileSystemSandboxPolicy::ReadOnly
896 );
897
898 let policy_external = SandboxPolicy::ExternalSandbox {
899 network_access: NetworkSandboxPolicy::Localhost,
900 };
901 assert_eq!(
902 policy_external.filesystem_policy(),
903 FileSystemSandboxPolicy::External
904 );
905
906 let policy_workspace = SandboxPolicy::WorkspaceWrite {
907 writable_roots: vec![PathBuf::from("/tmp"), PathBuf::from("/home")],
908 network_access: NetworkSandboxPolicy::FullAccess,
909 };
910 let fs_policy = policy_workspace.filesystem_policy();
911 match fs_policy {
912 FileSystemSandboxPolicy::WorkspaceWrite { writable_roots } => {
913 assert_eq!(writable_roots.len(), 2);
914 }
915 _ => panic!("Expected WorkspaceWrite variant"),
916 }
917 }
918
919 #[test]
920 #[cfg(target_os = "macos")]
921 fn test_sandbox_exec_request_carries_network_policy() {
922 let manager = SandboxManager::new();
924
925 let policy = SandboxPolicy::ReadOnly {
927 file_system: FileSystemSandboxPolicy::ReadOnly,
928 network_access: NetworkSandboxPolicy::NoAccess,
929 };
930 let command = SandboxCommand {
931 program: OsString::from("ls"),
932 args: vec![],
933 cwd: PathBuf::from("/tmp"),
934 env: HashMap::new(),
935 };
936 let request = manager.create_exec_request(command, policy).unwrap();
937 assert_eq!(request.network_policy, NetworkSandboxPolicy::NoAccess);
938
939 let policy_localhost = SandboxPolicy::ReadOnly {
941 file_system: FileSystemSandboxPolicy::ReadOnly,
942 network_access: NetworkSandboxPolicy::Localhost,
943 };
944 let command = SandboxCommand {
945 program: OsString::from("ls"),
946 args: vec![],
947 cwd: PathBuf::from("/tmp"),
948 env: HashMap::new(),
949 };
950 let request = manager
951 .create_exec_request(command, policy_localhost)
952 .unwrap();
953 assert_eq!(request.network_policy, NetworkSandboxPolicy::Localhost);
954 }
955
956 #[test]
957 #[cfg(not(target_os = "macos"))]
958 fn test_sandbox_exec_request_carries_network_policy() {
959 }
961
962 #[test]
963 #[cfg(target_os = "macos")]
964 fn test_sandbox_exec_request_carries_filesystem_policy() {
965 let manager = SandboxManager::new();
967
968 let policy = SandboxPolicy::ReadOnly {
970 file_system: FileSystemSandboxPolicy::ReadOnly,
971 network_access: NetworkSandboxPolicy::FullAccess,
972 };
973 let command = SandboxCommand {
974 program: OsString::from("ls"),
975 args: vec![],
976 cwd: PathBuf::from("/tmp"),
977 env: HashMap::new(),
978 };
979 let request = manager.create_exec_request(command, policy).unwrap();
980 assert_eq!(
981 request.file_system_policy,
982 FileSystemSandboxPolicy::ReadOnly
983 );
984
985 let policy_workspace = SandboxPolicy::WorkspaceWrite {
987 writable_roots: vec![PathBuf::from("/workspace")],
988 network_access: NetworkSandboxPolicy::FullAccess,
989 };
990 let command = SandboxCommand {
991 program: OsString::from("ls"),
992 args: vec![],
993 cwd: PathBuf::from("/tmp"),
994 env: HashMap::new(),
995 };
996 let request = manager
997 .create_exec_request(command, policy_workspace)
998 .unwrap();
999 match request.file_system_policy {
1000 FileSystemSandboxPolicy::WorkspaceWrite { writable_roots } => {
1001 assert_eq!(writable_roots.len(), 1);
1002 }
1003 _ => panic!("Expected WorkspaceWrite variant"),
1004 }
1005 }
1006
1007 #[test]
1008 #[cfg(not(target_os = "macos"))]
1009 fn test_sandbox_exec_request_carries_filesystem_policy() {
1010 }
1012}