Skip to main content
Glama
by 8b-is
security_vigilance.rs12.7 kB
//! Security Vigilance Mode - Smart Tree as your security sentinel! 🕵️‍♂️ //! //! Always watching for anomalies, even in the "boring" places where bad actors hide. //! Takes random samples, tracks recent modifications, and looks for suspicious patterns. use crate::scanner::FileNode; use chrono::{DateTime, Local, Duration}; use rand::{thread_rng, Rng}; use std::collections::{HashMap, VecDeque}; use std::fs; use std::io::Read; use std::path::{Path, PathBuf}; /// Security alert levels #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum AlertLevel { /// 🟢 Normal - Nothing suspicious Normal, /// 🟡 Interesting - Worth noting Interesting, /// 🟠 Suspicious - Potential issue Suspicious, /// 🔴 Critical - Definite security concern Critical, } impl AlertLevel { pub fn emoji(&self) -> &'static str { match self { Self::Normal => "🟢", Self::Interesting => "🟡", Self::Suspicious => "🟠", Self::Critical => "🔴", } } } /// Security finding #[derive(Debug, Clone)] pub struct SecurityFinding { pub path: PathBuf, pub alert_level: AlertLevel, pub reason: String, pub details: Option<String>, pub timestamp: DateTime<Local>, } /// Tracks recent file modifications #[derive(Debug)] pub struct RecentWriteTracker { /// Last N modified files per directory recent_writes: HashMap<PathBuf, VecDeque<(PathBuf, DateTime<Local>)>>, /// How many recent writes to track track_count: usize, } impl RecentWriteTracker { pub fn new(track_count: usize) -> Self { Self { recent_writes: HashMap::new(), track_count, } } pub fn add_file(&mut self, dir: &Path, file: &Path, modified: DateTime<Local>) { let writes = self.recent_writes .entry(dir.to_path_buf()) .or_insert_with(|| VecDeque::with_capacity(self.track_count)); writes.push_front((file.to_path_buf(), modified)); if writes.len() > self.track_count { writes.pop_back(); } } pub fn get_recent_writes(&self, dir: &Path) -> Vec<(PathBuf, DateTime<Local>)> { self.recent_writes .get(dir) .map(|deque| deque.iter().cloned().collect()) .unwrap_or_default() } } /// Security vigilance analyzer pub struct SecurityVigilance { /// Track recent writes write_tracker: RecentWriteTracker, /// Security findings findings: Vec<SecurityFinding>, /// Suspicious patterns to look for suspicious_patterns: Vec<(regex::Regex, String, AlertLevel)>, /// Suspicious file names suspicious_names: HashMap<String, (String, AlertLevel)>, /// Allowed sample size for file inspection max_sample_size: usize, /// Directories that should rarely change protected_paths: Vec<String>, } impl SecurityVigilance { pub fn new() -> Self { let mut suspicious_patterns = vec![]; let mut suspicious_names = HashMap::new(); // Suspicious content patterns suspicious_patterns.push(( regex::Regex::new(r"eval\s*\(|exec\s*\(").unwrap(), "Dynamic code execution detected".to_string(), AlertLevel::Suspicious, )); suspicious_patterns.push(( regex::Regex::new(r"(?i)(password|passwd|pwd)\s*=\s*[\"'][^\"']+[\"']").unwrap(), "Hardcoded password detected".to_string(), AlertLevel::Critical, )); suspicious_patterns.push(( regex::Regex::new(r"(?i)api[_-]?key\s*=\s*[\"'][^\"']+[\"']").unwrap(), "Hardcoded API key detected".to_string(), AlertLevel::Critical, )); suspicious_patterns.push(( regex::Regex::new(r"0x[0-9a-fA-F]{40,}").unwrap(), "Possible crypto wallet address".to_string(), AlertLevel::Interesting, )); suspicious_patterns.push(( regex::Regex::new(r"(?i)wget|curl.*http").unwrap(), "Network download command detected".to_string(), AlertLevel::Suspicious, )); suspicious_patterns.push(( regex::Regex::new(r"/etc/passwd|/etc/shadow").unwrap(), "System file access detected".to_string(), AlertLevel::Critical, )); // Suspicious file names suspicious_names.insert(".env.prod".to_string(), ("Production environment file".to_string(), AlertLevel::Suspicious)); suspicious_names.insert("id_rsa".to_string(), ("Private SSH key".to_string(), AlertLevel::Critical)); suspicious_names.insert(".npmrc".to_string(), ("NPM configuration with possible tokens".to_string(), AlertLevel::Interesting)); suspicious_names.insert("wallet.dat".to_string(), ("Cryptocurrency wallet file".to_string(), AlertLevel::Critical)); // Backdoor-ish names suspicious_names.insert("backdoor.js".to_string(), ("Suspicious filename: backdoor".to_string(), AlertLevel::Critical)); suspicious_names.insert("shell.php".to_string(), ("Web shell detected".to_string(), AlertLevel::Critical)); suspicious_names.insert("c99.php".to_string(), ("Known web shell name".to_string(), AlertLevel::Critical)); let protected_paths = vec![ "node_modules".to_string(), ".git".to_string(), "System32".to_string(), "/etc".to_string(), "/usr/bin".to_string(), "/usr/local/bin".to_string(), ]; Self { write_tracker: RecentWriteTracker::new(5), findings: Vec::new(), suspicious_patterns, suspicious_names, max_sample_size: 1024, // 1KB sample protected_paths, } } /// Analyze a file node for security issues pub fn analyze_node(&mut self, node: &FileNode, parent_path: &Path) { let full_path = parent_path.join(&node.name); // Check if this is a recent write if let Ok(metadata) = fs::metadata(&full_path) { if let Ok(modified) = metadata.modified() { let modified_time: DateTime<Local> = modified.into(); let now = Local::now(); // Track if modified in last 24 hours if now.signed_duration_since(modified_time) < Duration::hours(24) { self.write_tracker.add_file(parent_path, &full_path, modified_time); // Extra vigilance for recent writes in protected paths if self.is_protected_path(&full_path) { self.add_finding(SecurityFinding { path: full_path.clone(), alert_level: AlertLevel::Interesting, reason: "Recent modification in protected directory".to_string(), details: Some(format!("Modified: {}", modified_time.format("%Y-%m-%d %H:%M:%S"))), timestamp: now, }); } } } } // Check suspicious file names if let Some((reason, level)) = self.suspicious_names.get(&node.name.to_lowercase()) { self.add_finding(SecurityFinding { path: full_path.clone(), alert_level: *level, reason: reason.clone(), details: None, timestamp: Local::now(), }); } // Sample file content for suspicious patterns if !node.is_directory && node.size > 0 { self.sample_file_content(&full_path); } } /// Take a random sample from a file and check for suspicious patterns fn sample_file_content(&mut self, path: &Path) { // Skip binary files (simple heuristic based on extension) if let Some(ext) = path.extension() { let ext_str = ext.to_string_lossy().to_lowercase(); if matches!(ext_str.as_str(), "exe" | "dll" | "so" | "dylib" | "bin" | "jpg" | "png" | "mp4") { return; } } // Try to read a sample if let Ok(mut file) = fs::File::open(path) { let file_size = file.metadata().map(|m| m.len()).unwrap_or(0); if file_size > 0 { let mut buffer = vec![0u8; self.max_sample_size.min(file_size as usize)]; // Random offset for larger files if file_size > self.max_sample_size as u64 { let max_offset = file_size - self.max_sample_size as u64; let offset = thread_rng().gen_range(0..max_offset); let _ = file.seek(std::io::SeekFrom::Start(offset)); } if let Ok(bytes_read) = file.read(&mut buffer) { buffer.truncate(bytes_read); // Convert to string (lossy for non-UTF8) let content = String::from_utf8_lossy(&buffer); // Check patterns for (pattern, reason, level) in &self.suspicious_patterns { if pattern.is_match(&content) { self.add_finding(SecurityFinding { path: path.to_path_buf(), alert_level: *level, reason: reason.clone(), details: Some("Found in random sample".to_string()), timestamp: Local::now(), }); } } } } } } /// Check if a path is in a protected directory fn is_protected_path(&self, path: &Path) -> bool { let path_str = path.to_string_lossy().to_lowercase(); self.protected_paths.iter().any(|protected| { path_str.contains(&protected.to_lowercase()) }) } /// Add a security finding pub fn add_finding(&mut self, finding: SecurityFinding) { self.findings.push(finding); } /// Get all findings sorted by severity pub fn get_findings(&self) -> Vec<&SecurityFinding> { let mut findings: Vec<_> = self.findings.iter().collect(); findings.sort_by_key(|f| std::cmp::Reverse(f.alert_level)); findings } /// Get summary of findings pub fn summary(&self) -> String { let mut summary = String::from("🕵️ Security Vigilance Report\n\n"); let mut counts = HashMap::new(); for finding in &self.findings { *counts.entry(finding.alert_level).or_insert(0) += 1; } if counts.is_empty() { summary.push_str("✅ No security issues detected!\n"); } else { summary.push_str("Findings by severity:\n"); for level in [AlertLevel::Critical, AlertLevel::Suspicious, AlertLevel::Interesting, AlertLevel::Normal] { if let Some(count) = counts.get(&level) { summary.push_str(&format!(" {} {} findings\n", level.emoji(), count)); } } // Show critical findings summary.push_str("\n"); for finding in self.findings.iter().filter(|f| f.alert_level == AlertLevel::Critical) { summary.push_str(&format!( "{} {} - {}\n", finding.alert_level.emoji(), finding.path.display(), finding.reason )); } } summary } } // Vigilance patterns that could be in AI modes: // // 1. Always show last 5 modified files in each directory // 2. Random sampling from files to detect: // - Hardcoded secrets // - Malicious code patterns // - Suspicious network calls // - Backdoors // 3. Track modifications in "boring" directories like node_modules // 4. Alert on files that shouldn't exist (like .env.prod in git) // 5. Detect anomalies in system directories // Trisha says: "It's like having a security guard who actually checks // the boring supply closets where people hide things! Smart!" 🔍

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/8b-is/smart-tree'

If you have feedback or need assistance with the MCP directory API, please join our Discord server