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}