use super::*;
use std::collections::HashSet;
use std::path::Path;
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();
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"));
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();
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());
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, allowed_network_hosts: None,
}
}
pub fn new_permissive() -> Self {
Self {
blocked_paths: HashSet::new(),
dangerous_commands: HashSet::new(),
max_file_size: 1024 * 1024 * 1024, allowed_network_hosts: None,
}
}
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 => {
}
}
Ok(())
}
fn validate_file_access(&self, request: &PermissionRequest) -> Result<(), PermissionError> {
if let Some(path) = &request.path {
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())
));
}
}
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)
));
}
}
}
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(())
}
fn validate_directory_access(&self, request: &PermissionRequest) -> Result<(), PermissionError> {
if let Some(path) = &request.path {
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(())
}
fn validate_shell_command(&self, request: &PermissionRequest) -> Result<(), PermissionError> {
if let Some(command) = request.params.get("command").and_then(|v| v.as_str()) {
let base_command = command.split_whitespace().next().unwrap_or("");
if self.dangerous_commands.contains(base_command) {
return Err(PermissionError::SecurityViolation(
format!("Dangerous command '{}' requires explicit approval", base_command)
));
}
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(())
}
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)
));
}
}
}
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(())
}
fn validate_process_spawn(&self, request: &PermissionRequest) -> Result<(), PermissionError> {
if let Some(executable) = request.params.get("executable").and_then(|v| v.as_str()) {
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(())
}
pub fn add_blocked_path(&mut self, path: PathBuf) {
self.blocked_paths.insert(path);
}
pub fn add_dangerous_command(&mut self, command: String) {
self.dangerous_commands.insert(command);
}
pub fn set_allowed_network_hosts(&mut self, hosts: HashSet<String>) {
self.allowed_network_hosts = Some(hosts);
}
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());
}
}