use std::path::{Path, PathBuf};
use tracing::{debug, warn};
use crate::module::traits::ModuleError;
fn absolutize_path(path: &Path) -> PathBuf {
if path.as_os_str().is_empty() {
return PathBuf::from(".");
}
if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir()
.unwrap_or_else(|_| PathBuf::from("."))
.join(path)
}
}
pub struct FileSystemSandbox {
allowed_path: PathBuf,
}
impl FileSystemSandbox {
pub fn new<P: AsRef<Path>>(data_dir: P) -> Self {
let abs = absolutize_path(data_dir.as_ref());
let allowed_path = std::fs::canonicalize(&abs).unwrap_or(abs);
Self { allowed_path }
}
pub fn validate_path<P: AsRef<Path>>(&self, path: P) -> Result<PathBuf, ModuleError> {
let path = path.as_ref();
if path.is_absolute() && path.starts_with(&self.allowed_path) {
}
let canonical = path.canonicalize().map_err(|e| {
ModuleError::OperationError(format!("Failed to canonicalize path {path:?}: {e}"))
})?;
if !canonical.starts_with(&self.allowed_path) {
warn!(
"Module attempted to access path outside sandbox: {:?} (allowed: {:?})",
canonical, self.allowed_path
);
return Err(ModuleError::OperationError(format!(
"Access denied: path {:?} is outside allowed directory {:?}",
canonical, self.allowed_path
)));
}
debug!("Path validated: {:?} is within sandbox", canonical);
Ok(canonical)
}
pub fn allowed_path(&self) -> &Path {
&self.allowed_path
}
#[inline]
pub fn is_within_sandbox<P: AsRef<Path>>(&self, path: P) -> bool {
let path = path.as_ref();
if !path.starts_with(&self.allowed_path) {
return false;
}
if let Ok(canonical) = path.canonicalize() {
canonical.starts_with(&self.allowed_path)
} else {
true
}
}
}