relay_core_lib/utils/
path.rs1use std::io;
2use std::path::{Path, PathBuf};
3
4#[derive(Debug, Clone)]
6pub struct PathSanitizer {
7 root: PathBuf,
8}
9
10impl PathSanitizer {
11 pub fn new(root: PathBuf) -> Self {
12 let canonical_root = root.canonicalize().unwrap_or(root);
15 Self {
16 root: canonical_root,
17 }
18 }
19
20 pub fn sanitize(&self, path_str: &str) -> io::Result<PathBuf> {
23 let path = Path::new(path_str);
24
25 let candidate = if path.is_absolute() {
28 path.to_path_buf()
29 } else {
30 self.root.join(path)
31 };
32
33 let canonical = candidate.canonicalize()?;
37
38 if canonical.starts_with(&self.root) {
39 Ok(canonical)
40 } else {
41 Err(io::Error::new(
42 io::ErrorKind::PermissionDenied,
43 format!("Path traversal detected: {:?}", canonical),
44 ))
45 }
46 }
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52 use std::fs;
53 use tempfile::TempDir;
54
55 #[test]
56 fn test_sandbox_allows_valid_file() {
57 let temp_dir = TempDir::new().unwrap();
58 let file_path = temp_dir.path().join("test.txt");
59 fs::write(&file_path, "content").unwrap();
60
61 let sanitizer = PathSanitizer::new(temp_dir.path().to_path_buf());
62 let result = sanitizer.sanitize("test.txt");
63
64 assert!(result.is_ok());
65 assert_eq!(result.unwrap(), file_path.canonicalize().unwrap());
66 }
67
68 #[test]
69 fn test_sandbox_denies_traversal() {
70 let temp_dir = TempDir::new().unwrap();
71 let sanitizer = PathSanitizer::new(temp_dir.path().to_path_buf());
72
73 let _result = sanitizer.sanitize("../outside.txt");
76 let outside_dir = TempDir::new().unwrap();
81 let outside_file = outside_dir.path().join("outside.txt");
82 fs::write(&outside_file, "secret").unwrap();
83
84 let result = sanitizer.sanitize(outside_file.to_str().unwrap());
86
87 assert!(result.is_err());
88 }
89}