agpm_cli/utils/
security.rs

1//! Security utilities for path validation and access control
2//!
3//! This module provides security functions to prevent unauthorized access
4//! to sensitive system directories and validate paths for safe operations.
5
6use std::path::Path;
7
8/// Security blacklist for local paths
9/// Prevents access to sensitive system directories while allowing normal development paths
10pub static BLACKLISTED_PATHS: &[&str] = &[
11    "/etc",                    // System configuration
12    "/sys",                    // System information
13    "/proc",                   // Process information
14    "/dev",                    // Device files
15    "/boot",                   // Boot files
16    "/root",                   // Root home
17    "/bin",                    // System binaries
18    "/sbin",                   // System binaries
19    "/usr/bin",                // User binaries
20    "/usr/sbin",               // User system binaries
21    "/System",                 // macOS system
22    "/Library",                // macOS system libraries
23    "/private/etc",            // macOS etc
24    "/private/var/db",         // macOS system databases
25    "C:\\Windows",             // Windows system
26    "C:\\Program Files",       // Windows programs
27    "C:\\Program Files (x86)", // Windows 32-bit programs
28    "C:\\ProgramData",         // Windows program data
29    "C:\\System",              // Windows system
30    "C:\\System32",            // Windows system32
31    "C:\\Windows\\System32",   // Windows system32
32];
33
34/// Check if a path is blacklisted (points to sensitive system directories)
35///
36/// # Arguments
37/// * `path` - The path to check
38///
39/// # Returns
40/// * `true` if the path is blacklisted, `false` otherwise
41///
42/// # Examples
43/// ```
44/// use agpm_cli::utils::security::is_path_blacklisted;
45/// use std::path::Path;
46///
47/// assert!(is_path_blacklisted(Path::new("/etc/passwd")));
48/// assert!(is_path_blacklisted(Path::new("/System/Library")));
49/// assert!(!is_path_blacklisted(Path::new("/home/user/project")));
50/// ```
51#[must_use]
52pub fn is_path_blacklisted(path: &Path) -> bool {
53    for blacklisted in BLACKLISTED_PATHS {
54        if path.starts_with(blacklisted) {
55            return true;
56        }
57    }
58    false
59}
60
61/// Validates a path for security constraints
62///
63/// Checks if the path:
64/// - Is not blacklisted
65/// - Does not contain symlinks (if `check_symlinks` is true)
66///
67/// # Arguments
68/// * `path` - The path to validate (should be the original path before canonicalization)
69/// * `check_symlinks` - Whether to check for symlinks in the path
70///
71/// # Returns
72/// * `Ok(())` if the path is safe
73/// * `Err` with a descriptive error message if the path fails validation
74pub fn validate_path_security(path: &Path, check_symlinks: bool) -> anyhow::Result<()> {
75    // Check blacklist
76    if is_path_blacklisted(path) {
77        return Err(anyhow::anyhow!("Security error: Access to system directories is not allowed"));
78    }
79
80    // Check for symlinks if requested
81    if check_symlinks && path.exists() {
82        let metadata = std::fs::symlink_metadata(path)
83            .map_err(|_| anyhow::anyhow!("Failed to check path metadata"))?;
84        if metadata.file_type().is_symlink() {
85            return Err(anyhow::anyhow!(
86                "Security error: Symlinks are not allowed in local dependency paths"
87            ));
88        }
89    }
90
91    Ok(())
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_blacklisted_paths() {
100        // System paths should be blacklisted
101        assert!(is_path_blacklisted(Path::new("/etc/passwd")));
102        assert!(is_path_blacklisted(Path::new("/sys/kernel")));
103        assert!(is_path_blacklisted(Path::new("/System/Library/CoreServices")));
104        assert!(is_path_blacklisted(Path::new("/private/etc/hosts")));
105
106        #[cfg(windows)]
107        {
108            assert!(is_path_blacklisted(Path::new("C:\\Windows\\System32")));
109            assert!(is_path_blacklisted(Path::new("C:\\Program Files\\App")));
110        }
111
112        // Normal development paths should not be blacklisted
113        assert!(!is_path_blacklisted(Path::new("/home/user/project")));
114        assert!(!is_path_blacklisted(Path::new("/tmp/test")));
115        assert!(!is_path_blacklisted(Path::new("/var/folders/temp")));
116        assert!(!is_path_blacklisted(Path::new("/Users/developer/work")));
117    }
118
119    #[test]
120    fn test_validate_path_security() -> anyhow::Result<()> {
121        // Safe paths should pass
122        validate_path_security(Path::new("/home/user/project"), false)?;
123        validate_path_security(Path::new("/tmp/test"), false)?;
124
125        // Blacklisted paths should fail
126        assert!(validate_path_security(Path::new("/etc/passwd"), false).is_err());
127        assert!(validate_path_security(Path::new("/System/Library"), false).is_err());
128        Ok(())
129    }
130}