coderlib 0.1.0

A Rust library for AI-powered code assistance and agentic system
Documentation
//! Security Validator for Permission Requests

use super::*;
use std::collections::HashSet;
use std::path::Path;

/// Security validator for permission requests
pub struct SecurityValidator {
    blocked_paths: HashSet<PathBuf>,
    dangerous_commands: HashSet<String>,
    max_file_size: u64,
    allowed_network_hosts: Option<HashSet<String>>,
}

impl SecurityValidator {
    pub fn new() -> Self {
        let mut blocked_paths = HashSet::new();
        
        // Unix system paths
        blocked_paths.insert(PathBuf::from("/etc"));
        blocked_paths.insert(PathBuf::from("/sys"));
        blocked_paths.insert(PathBuf::from("/proc"));
        blocked_paths.insert(PathBuf::from("/dev"));
        blocked_paths.insert(PathBuf::from("/boot"));
        blocked_paths.insert(PathBuf::from("/root"));
        
        // Windows system paths
        blocked_paths.insert(PathBuf::from("C:\\Windows"));
        blocked_paths.insert(PathBuf::from("C:\\System32"));
        blocked_paths.insert(PathBuf::from("C:\\Program Files"));
        blocked_paths.insert(PathBuf::from("C:\\Program Files (x86)"));
        
        let mut dangerous_commands = HashSet::new();
        
        // Unix dangerous commands
        dangerous_commands.insert("rm".to_string());
        dangerous_commands.insert("rmdir".to_string());
        dangerous_commands.insert("dd".to_string());
        dangerous_commands.insert("mkfs".to_string());
        dangerous_commands.insert("fdisk".to_string());
        dangerous_commands.insert("sudo".to_string());
        dangerous_commands.insert("su".to_string());
        dangerous_commands.insert("chmod".to_string());
        dangerous_commands.insert("chown".to_string());
        
        // Windows dangerous commands
        dangerous_commands.insert("del".to_string());
        dangerous_commands.insert("rmdir".to_string());
        dangerous_commands.insert("format".to_string());
        dangerous_commands.insert("diskpart".to_string());
        dangerous_commands.insert("reg".to_string());
        dangerous_commands.insert("net".to_string());
        
        Self {
            blocked_paths,
            dangerous_commands,
            max_file_size: 100 * 1024 * 1024, // 100MB
            allowed_network_hosts: None,
        }
    }
    
    /// Create a permissive validator for development
    pub fn new_permissive() -> Self {
        Self {
            blocked_paths: HashSet::new(),
            dangerous_commands: HashSet::new(),
            max_file_size: 1024 * 1024 * 1024, // 1GB
            allowed_network_hosts: None,
        }
    }
    
    /// Validate a permission request
    pub fn validate_request(&self, request: &PermissionRequest) -> Result<(), PermissionError> {
        match request.permission {
            Permission::FileRead | Permission::FileWrite | Permission::FileDelete => {
                self.validate_file_access(request)?;
            }
            Permission::DirectoryList | Permission::DirectoryCreate => {
                self.validate_directory_access(request)?;
            }
            Permission::ShellCommand => {
                self.validate_shell_command(request)?;
            }
            Permission::NetworkAccess => {
                self.validate_network_access(request)?;
            }
            Permission::ProcessSpawn => {
                self.validate_process_spawn(request)?;
            }
            Permission::SystemInfo => {
                // System info is generally safe
            }
        }
        
        Ok(())
    }
    
    /// Validate file access permissions
    fn validate_file_access(&self, request: &PermissionRequest) -> Result<(), PermissionError> {
        if let Some(path) = &request.path {
            // Check if path is in blocked list
            for blocked in &self.blocked_paths {
                if path.starts_with(blocked) {
                    return Err(PermissionError::SecurityViolation(
                        format!("Access to system path '{}' is not allowed", path.display())
                    ));
                }
            }
            
            // Check file size for write operations
            if request.permission == Permission::FileWrite {
                if let Some(size) = request.params.get("size").and_then(|v| v.as_u64()) {
                    if size > self.max_file_size {
                        return Err(PermissionError::SecurityViolation(
                            format!("File size {} exceeds maximum allowed size {}", size, self.max_file_size)
                        ));
                    }
                }
            }
            
            // Check for suspicious file extensions
            if let Some(extension) = path.extension().and_then(|e| e.to_str()) {
                match extension.to_lowercase().as_str() {
                    "exe" | "bat" | "cmd" | "ps1" | "sh" | "bash" => {
                        if request.permission == Permission::FileWrite {
                            return Err(PermissionError::SecurityViolation(
                                format!("Writing executable files (.{}) requires explicit approval", extension)
                            ));
                        }
                    }
                    _ => {}
                }
            }
        }
        
        Ok(())
    }
    
    /// Validate directory access permissions
    fn validate_directory_access(&self, request: &PermissionRequest) -> Result<(), PermissionError> {
        if let Some(path) = &request.path {
            // Check if path is in blocked list
            for blocked in &self.blocked_paths {
                if path.starts_with(blocked) {
                    return Err(PermissionError::SecurityViolation(
                        format!("Access to system directory '{}' is not allowed", path.display())
                    ));
                }
            }
        }
        
        Ok(())
    }
    
    /// Validate shell command execution
    fn validate_shell_command(&self, request: &PermissionRequest) -> Result<(), PermissionError> {
        if let Some(command) = request.params.get("command").and_then(|v| v.as_str()) {
            // Extract the base command (first word)
            let base_command = command.split_whitespace().next().unwrap_or("");
            
            // Check if command is in dangerous list
            if self.dangerous_commands.contains(base_command) {
                return Err(PermissionError::SecurityViolation(
                    format!("Dangerous command '{}' requires explicit approval", base_command)
                ));
            }
            
            // Check for suspicious patterns
            if command.contains("rm -rf") || command.contains("del /s") {
                return Err(PermissionError::SecurityViolation(
                    "Recursive deletion commands require explicit approval".to_string()
                ));
            }
            
            if command.contains("sudo") || command.contains("su ") {
                return Err(PermissionError::SecurityViolation(
                    "Privilege escalation commands require explicit approval".to_string()
                ));
            }
        }
        
        Ok(())
    }
    
    /// Validate network access
    fn validate_network_access(&self, request: &PermissionRequest) -> Result<(), PermissionError> {
        if let Some(allowed_hosts) = &self.allowed_network_hosts {
            if let Some(host) = request.params.get("host").and_then(|v| v.as_str()) {
                if !allowed_hosts.contains(host) {
                    return Err(PermissionError::SecurityViolation(
                        format!("Network access to '{}' is not allowed", host)
                    ));
                }
            }
        }
        
        // Check for suspicious URLs
        if let Some(url) = request.params.get("url").and_then(|v| v.as_str()) {
            if url.starts_with("file://") {
                return Err(PermissionError::SecurityViolation(
                    "File URLs are not allowed for network access".to_string()
                ));
            }
        }
        
        Ok(())
    }
    
    /// Validate process spawning
    fn validate_process_spawn(&self, request: &PermissionRequest) -> Result<(), PermissionError> {
        if let Some(executable) = request.params.get("executable").and_then(|v| v.as_str()) {
            // Check if executable is in dangerous list
            let exe_name = Path::new(executable)
                .file_name()
                .and_then(|n| n.to_str())
                .unwrap_or(executable);
            
            if self.dangerous_commands.contains(exe_name) {
                return Err(PermissionError::SecurityViolation(
                    format!("Spawning dangerous process '{}' requires explicit approval", exe_name)
                ));
            }
        }
        
        Ok(())
    }
    
    /// Add a blocked path
    pub fn add_blocked_path(&mut self, path: PathBuf) {
        self.blocked_paths.insert(path);
    }
    
    /// Add a dangerous command
    pub fn add_dangerous_command(&mut self, command: String) {
        self.dangerous_commands.insert(command);
    }
    
    /// Set allowed network hosts
    pub fn set_allowed_network_hosts(&mut self, hosts: HashSet<String>) {
        self.allowed_network_hosts = Some(hosts);
    }
    
    /// Set maximum file size
    pub fn set_max_file_size(&mut self, size: u64) {
        self.max_file_size = size;
    }
}

impl Default for SecurityValidator {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_validate_safe_file_access() {
        let validator = SecurityValidator::new();
        
        let request = create_permission_request(
            "test-session",
            "file_tool",
            Permission::FileRead,
            "Read user file",
            "read",
            Some(PathBuf::from("/home/user/document.txt")),
            serde_json::json!({}),
        );
        
        assert!(validator.validate_request(&request).is_ok());
    }
    
    #[test]
    fn test_validate_blocked_path() {
        let validator = SecurityValidator::new();
        
        let request = create_permission_request(
            "test-session",
            "file_tool",
            Permission::FileRead,
            "Read system file",
            "read",
            Some(PathBuf::from("/etc/passwd")),
            serde_json::json!({}),
        );
        
        assert!(validator.validate_request(&request).is_err());
    }
    
    #[test]
    fn test_validate_dangerous_command() {
        let validator = SecurityValidator::new();
        
        let request = create_permission_request(
            "test-session",
            "shell_tool",
            Permission::ShellCommand,
            "Delete files",
            "execute",
            None,
            serde_json::json!({"command": "rm -rf /"}),
        );
        
        assert!(validator.validate_request(&request).is_err());
    }
    
    #[test]
    fn test_permissive_validator() {
        let validator = SecurityValidator::new_permissive();
        
        let request = create_permission_request(
            "test-session",
            "file_tool",
            Permission::FileRead,
            "Read system file",
            "read",
            Some(PathBuf::from("/etc/passwd")),
            serde_json::json!({}),
        );
        
        assert!(validator.validate_request(&request).is_ok());
    }
}