gravityfile_plugin/
sandbox.rs1use std::collections::HashSet;
7use std::path::PathBuf;
8
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14pub enum Permission {
15 Read,
17
18 Write,
20
21 Execute,
23
24 Network,
26
27 Environment,
29
30 Clipboard,
32
33 Ui,
35
36 Notify,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct SandboxConfig {
43 pub allowed_read_paths: Vec<PathBuf>,
45
46 pub allowed_write_paths: Vec<PathBuf>,
48
49 pub allowed_commands: HashSet<String>,
51
52 pub allow_network: bool,
54
55 pub allow_env: bool,
57
58 pub timeout_ms: u64,
60
61 pub max_memory: usize,
63
64 pub max_read_size: usize,
66
67 pub permissions: HashSet<Permission>,
69}
70
71impl Default for SandboxConfig {
72 fn default() -> Self {
73 let mut permissions = HashSet::new();
74 permissions.insert(Permission::Read);
75 permissions.insert(Permission::Notify);
76
77 Self {
78 allowed_read_paths: vec![],
79 allowed_write_paths: vec![],
80 allowed_commands: HashSet::new(),
81 allow_network: false,
82 allow_env: false,
83 timeout_ms: 5000,
84 max_memory: 256 * 1024 * 1024, max_read_size: 10 * 1024 * 1024, permissions,
87 }
88 }
89}
90
91impl SandboxConfig {
92 pub fn minimal() -> Self {
94 Self {
95 allowed_read_paths: vec![],
96 allowed_write_paths: vec![],
97 allowed_commands: HashSet::new(),
98 allow_network: false,
99 allow_env: false,
100 timeout_ms: 1000,
101 max_memory: 64 * 1024 * 1024,
102 max_read_size: 1024 * 1024,
103 permissions: HashSet::new(),
104 }
105 }
106
107 pub fn permissive() -> Self {
114 let mut permissions = HashSet::new();
115 permissions.insert(Permission::Read);
116 permissions.insert(Permission::Write);
117 permissions.insert(Permission::Environment);
118 permissions.insert(Permission::Ui);
119 permissions.insert(Permission::Notify);
120
121 Self {
122 allowed_read_paths: vec![PathBuf::from("/")],
123 allowed_write_paths: vec![],
124 allowed_commands: HashSet::new(),
125 allow_network: false,
126 allow_env: true,
127 timeout_ms: 30000,
128 max_memory: 1024 * 1024 * 1024,
129 max_read_size: 100 * 1024 * 1024,
130 permissions,
131 }
132 }
133
134 pub fn allow_read(mut self, path: impl Into<PathBuf>) -> Self {
136 self.allowed_read_paths.push(path.into());
137 self.permissions.insert(Permission::Read);
138 self
139 }
140
141 pub fn allow_write(mut self, path: impl Into<PathBuf>) -> Self {
143 self.allowed_write_paths.push(path.into());
144 self.permissions.insert(Permission::Write);
145 self
146 }
147
148 pub fn allow_command(mut self, cmd: impl Into<String>) -> Self {
150 self.allowed_commands.insert(cmd.into());
151 self.permissions.insert(Permission::Execute);
152 self
153 }
154
155 pub fn allow_network(mut self) -> Self {
157 self.allow_network = true;
158 self.permissions.insert(Permission::Network);
159 self
160 }
161
162 pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
164 self.timeout_ms = timeout_ms;
165 self
166 }
167
168 pub fn with_memory_limit(mut self, bytes: usize) -> Self {
170 self.max_memory = bytes;
171 self
172 }
173
174 pub fn grant(mut self, permission: Permission) -> Self {
176 self.permissions.insert(permission);
177 self
178 }
179
180 pub fn has_permission(&self, permission: Permission) -> bool {
182 self.permissions.contains(&permission)
183 }
184
185 pub fn can_read(&self, path: &std::path::Path) -> bool {
190 if !self.has_permission(Permission::Read) {
191 return false;
192 }
193 if self.allowed_read_paths.is_empty() {
194 return false;
195 }
196
197 let Some(normalized) = self.normalize_path(path) else {
198 return false;
199 };
200 self.allowed_read_paths
201 .iter()
202 .filter_map(|p| dunce::canonicalize(p).ok())
203 .any(|allowed| normalized.starts_with(&allowed))
204 }
205
206 pub fn can_write(&self, path: &std::path::Path) -> bool {
211 if !self.has_permission(Permission::Write) {
212 return false;
213 }
214 if self.allowed_write_paths.is_empty() {
215 return false;
216 }
217
218 let Some(normalized) = self.normalize_path(path) else {
219 return false;
220 };
221 self.allowed_write_paths
222 .iter()
223 .filter_map(|p| dunce::canonicalize(p).ok())
224 .any(|allowed| normalized.starts_with(&allowed))
225 }
226
227 fn normalize_path(&self, path: &std::path::Path) -> Option<PathBuf> {
232 if let Ok(c) = dunce::canonicalize(path) {
234 return Some(c);
235 }
236
237 if let Some(parent) = path.parent()
240 && let Ok(c) = dunce::canonicalize(parent)
241 && let Some(name) = path.file_name()
242 {
243 return Some(c.join(name));
244 }
245
246 None
248 }
249
250 pub fn can_execute(&self, command: &str) -> bool {
252 if !self.has_permission(Permission::Execute) {
253 return false;
254 }
255 self.allowed_commands.contains("*") || self.allowed_commands.contains(command)
256 }
257}