gravityfile_plugin/
sandbox.rs

1//! Security sandboxing for plugins.
2//!
3//! This module provides sandboxing capabilities to restrict what plugins
4//! can access and do.
5
6use std::collections::HashSet;
7use std::path::PathBuf;
8
9use serde::{Deserialize, Serialize};
10
11/// Permission types that can be granted to plugins.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14pub enum Permission {
15    /// Read files from the filesystem.
16    Read,
17
18    /// Write files to the filesystem.
19    Write,
20
21    /// Execute external commands.
22    Execute,
23
24    /// Access network resources.
25    Network,
26
27    /// Access environment variables.
28    Environment,
29
30    /// Access clipboard.
31    Clipboard,
32
33    /// Modify UI elements.
34    Ui,
35
36    /// Send notifications.
37    Notify,
38}
39
40/// Configuration for plugin sandboxing.
41#[derive(Debug, Clone)]
42pub struct SandboxConfig {
43    /// Paths the plugin is allowed to read from.
44    pub allowed_read_paths: Vec<PathBuf>,
45
46    /// Paths the plugin is allowed to write to.
47    pub allowed_write_paths: Vec<PathBuf>,
48
49    /// Commands the plugin is allowed to execute.
50    pub allowed_commands: HashSet<String>,
51
52    /// Whether network access is allowed.
53    pub allow_network: bool,
54
55    /// Whether environment access is allowed.
56    pub allow_env: bool,
57
58    /// Maximum execution time in milliseconds.
59    pub timeout_ms: u64,
60
61    /// Maximum memory in bytes (0 = unlimited).
62    pub max_memory: usize,
63
64    /// Maximum file size that can be read.
65    pub max_read_size: usize,
66
67    /// Granted permissions.
68    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, // 256 MB
85            max_read_size: 10 * 1024 * 1024, // 10 MB
86            permissions,
87        }
88    }
89}
90
91impl SandboxConfig {
92    /// Create a minimal sandbox with no permissions.
93    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    /// Create a permissive sandbox (for trusted plugins).
108    pub fn permissive() -> Self {
109        let mut permissions = HashSet::new();
110        permissions.insert(Permission::Read);
111        permissions.insert(Permission::Write);
112        permissions.insert(Permission::Execute);
113        permissions.insert(Permission::Environment);
114        permissions.insert(Permission::Ui);
115        permissions.insert(Permission::Notify);
116
117        Self {
118            allowed_read_paths: vec![PathBuf::from("/")],
119            allowed_write_paths: vec![],
120            allowed_commands: HashSet::from(["*".to_string()]),
121            allow_network: false,
122            allow_env: true,
123            timeout_ms: 30000,
124            max_memory: 1024 * 1024 * 1024,
125            max_read_size: 100 * 1024 * 1024,
126            permissions,
127        }
128    }
129
130    /// Add an allowed read path.
131    pub fn allow_read(mut self, path: impl Into<PathBuf>) -> Self {
132        self.allowed_read_paths.push(path.into());
133        self.permissions.insert(Permission::Read);
134        self
135    }
136
137    /// Add an allowed write path.
138    pub fn allow_write(mut self, path: impl Into<PathBuf>) -> Self {
139        self.allowed_write_paths.push(path.into());
140        self.permissions.insert(Permission::Write);
141        self
142    }
143
144    /// Allow a specific command.
145    pub fn allow_command(mut self, cmd: impl Into<String>) -> Self {
146        self.allowed_commands.insert(cmd.into());
147        self.permissions.insert(Permission::Execute);
148        self
149    }
150
151    /// Enable network access.
152    pub fn allow_network(mut self) -> Self {
153        self.allow_network = true;
154        self.permissions.insert(Permission::Network);
155        self
156    }
157
158    /// Set timeout.
159    pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
160        self.timeout_ms = timeout_ms;
161        self
162    }
163
164    /// Set memory limit.
165    pub fn with_memory_limit(mut self, bytes: usize) -> Self {
166        self.max_memory = bytes;
167        self
168    }
169
170    /// Grant a permission.
171    pub fn grant(mut self, permission: Permission) -> Self {
172        self.permissions.insert(permission);
173        self
174    }
175
176    /// Check if a permission is granted.
177    pub fn has_permission(&self, permission: Permission) -> bool {
178        self.permissions.contains(&permission)
179    }
180
181    /// Check if reading a path is allowed.
182    pub fn can_read(&self, path: &PathBuf) -> bool {
183        if !self.has_permission(Permission::Read) {
184            return false;
185        }
186        if self.allowed_read_paths.is_empty() {
187            return true; // No restrictions
188        }
189        self.allowed_read_paths
190            .iter()
191            .any(|allowed| path.starts_with(allowed))
192    }
193
194    /// Check if writing to a path is allowed.
195    pub fn can_write(&self, path: &PathBuf) -> bool {
196        if !self.has_permission(Permission::Write) {
197            return false;
198        }
199        self.allowed_write_paths
200            .iter()
201            .any(|allowed| path.starts_with(allowed))
202    }
203
204    /// Check if executing a command is allowed.
205    pub fn can_execute(&self, command: &str) -> bool {
206        if !self.has_permission(Permission::Execute) {
207            return false;
208        }
209        self.allowed_commands.contains("*") || self.allowed_commands.contains(command)
210    }
211}
212
213/// Sandbox violation that occurred during plugin execution.
214#[derive(Debug, Clone)]
215#[allow(dead_code)]
216pub struct SandboxViolation {
217    /// Type of violation.
218    pub kind: ViolationKind,
219
220    /// Description of what was attempted.
221    pub description: String,
222
223    /// Path involved (if applicable).
224    pub path: Option<PathBuf>,
225
226    /// Command involved (if applicable).
227    pub command: Option<String>,
228}
229
230/// Types of sandbox violations.
231#[derive(Debug, Clone, Copy, PartialEq, Eq)]
232#[allow(dead_code)]
233pub enum ViolationKind {
234    /// Attempted to read from disallowed path.
235    ReadDenied,
236
237    /// Attempted to write to disallowed path.
238    WriteDenied,
239
240    /// Attempted to execute disallowed command.
241    ExecuteDenied,
242
243    /// Attempted network access when not allowed.
244    NetworkDenied,
245
246    /// Attempted to access environment when not allowed.
247    EnvDenied,
248
249    /// Exceeded memory limit.
250    MemoryExceeded,
251
252    /// Exceeded execution timeout.
253    TimeoutExceeded,
254
255    /// Attempted to read file larger than limit.
256    FileTooLarge,
257}
258
259impl std::fmt::Display for SandboxViolation {
260    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
261        match self.kind {
262            ViolationKind::ReadDenied => {
263                write!(f, "Read denied: {}", self.description)
264            }
265            ViolationKind::WriteDenied => {
266                write!(f, "Write denied: {}", self.description)
267            }
268            ViolationKind::ExecuteDenied => {
269                write!(f, "Execute denied: {}", self.description)
270            }
271            ViolationKind::NetworkDenied => {
272                write!(f, "Network access denied")
273            }
274            ViolationKind::EnvDenied => {
275                write!(f, "Environment access denied")
276            }
277            ViolationKind::MemoryExceeded => {
278                write!(f, "Memory limit exceeded")
279            }
280            ViolationKind::TimeoutExceeded => {
281                write!(f, "Execution timeout exceeded")
282            }
283            ViolationKind::FileTooLarge => {
284                write!(f, "File too large: {}", self.description)
285            }
286        }
287    }
288}
289
290impl std::error::Error for SandboxViolation {}