evalbox_sandbox/notify/
virtual_fs.rs1use std::collections::HashMap;
16use std::path::{Path, PathBuf};
17
18#[derive(Debug, Clone)]
20pub struct VirtualFs {
21 mappings: HashMap<PathBuf, PathBuf>,
23}
24
25impl VirtualFs {
26 pub fn new(workspace_root: &Path) -> Self {
28 let mut mappings = HashMap::new();
29 mappings.insert(PathBuf::from("/work"), workspace_root.join("work"));
30 mappings.insert(PathBuf::from("/tmp"), workspace_root.join("tmp"));
31 mappings.insert(PathBuf::from("/home"), workspace_root.join("home"));
32 Self { mappings }
33 }
34
35 pub fn empty() -> Self {
37 Self {
38 mappings: HashMap::new(),
39 }
40 }
41
42 pub fn add_mapping(&mut self, virtual_path: impl Into<PathBuf>, real_path: impl Into<PathBuf>) {
44 self.mappings.insert(virtual_path.into(), real_path.into());
45 }
46
47 pub fn translate(&self, path: &str) -> Option<PathBuf> {
52 let path = Path::new(path);
53 for (virtual_prefix, real_prefix) in &self.mappings {
54 if let Ok(suffix) = path.strip_prefix(virtual_prefix) {
55 return Some(real_prefix.join(suffix));
56 }
57 }
58 None
59 }
60
61 pub fn is_allowed(&self, path: &str) -> bool {
65 let path = Path::new(path);
66
67 for virtual_prefix in self.mappings.keys() {
69 if path.starts_with(virtual_prefix) {
70 return true;
71 }
72 }
73
74 let system_prefixes = ["/usr", "/bin", "/lib", "/lib64", "/etc", "/proc", "/dev"];
76 for prefix in &system_prefixes {
77 if path.starts_with(prefix) {
78 return true;
79 }
80 }
81
82 false
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn default_mappings() {
92 let vfs = VirtualFs::new(Path::new("/tmp/evalbox-abc123"));
93
94 assert_eq!(
95 vfs.translate("/work/main.py"),
96 Some(PathBuf::from("/tmp/evalbox-abc123/work/main.py"))
97 );
98 assert_eq!(
99 vfs.translate("/tmp/output.txt"),
100 Some(PathBuf::from("/tmp/evalbox-abc123/tmp/output.txt"))
101 );
102 assert_eq!(
103 vfs.translate("/home/.bashrc"),
104 Some(PathBuf::from("/tmp/evalbox-abc123/home/.bashrc"))
105 );
106 }
107
108 #[test]
109 fn no_translation_for_system_paths() {
110 let vfs = VirtualFs::new(Path::new("/tmp/evalbox-abc123"));
111 assert_eq!(vfs.translate("/usr/bin/python3"), None);
112 assert_eq!(vfs.translate("/etc/passwd"), None);
113 }
114
115 #[test]
116 fn is_allowed_checks() {
117 let vfs = VirtualFs::new(Path::new("/tmp/evalbox-abc123"));
118
119 assert!(vfs.is_allowed("/work/test.py"));
120 assert!(vfs.is_allowed("/tmp/output"));
121 assert!(vfs.is_allowed("/usr/bin/python3"));
122 assert!(vfs.is_allowed("/etc/passwd"));
123 assert!(vfs.is_allowed("/proc/self/status"));
124 assert!(!vfs.is_allowed("/root/.ssh/id_rsa"));
125 assert!(!vfs.is_allowed("/var/log/syslog"));
126 }
127
128 #[test]
129 fn custom_mapping() {
130 let mut vfs = VirtualFs::empty();
131 vfs.add_mapping("/data", "/mnt/shared/data");
132
133 assert_eq!(
134 vfs.translate("/data/file.csv"),
135 Some(PathBuf::from("/mnt/shared/data/file.csv"))
136 );
137 assert_eq!(vfs.translate("/work/test"), None);
138 }
139}