using MarketAlly.AIPlugin; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace MarketAlly.AIPlugin.Security.Plugins { /// /// Comprehensive security analysis including hardcoded secrets, vulnerabilities, and security patterns /// [AIPlugin("SecurityScan", "Comprehensive security analysis including hardcoded secrets, vulnerabilities, and security patterns")] public class SecurityScanPlugin : IAIPlugin { [AIParameter("Full path to the file or directory to scan", required: true)] public string Path { get; set; } [AIParameter("Scan for hardcoded secrets (API keys, passwords)", required: false)] public bool ScanSecrets { get; set; } = true; [AIParameter("Scan for SQL injection vulnerabilities", required: false)] public bool ScanSqlInjection { get; set; } = true; [AIParameter("Scan for XSS vulnerabilities", required: false)] public bool ScanXss { get; set; } = true; [AIParameter("Scan for insecure configurations", required: false)] public bool ScanConfigurations { get; set; } = true; [AIParameter("Security scan severity level: low, medium, high, critical", required: false)] public string SeverityLevel { get; set; } = "medium"; public IReadOnlyDictionary SupportedParameters => new Dictionary { ["path"] = typeof(string), ["scanSecrets"] = typeof(bool), ["scanSqlInjection"] = typeof(bool), ["scanXss"] = typeof(bool), ["scanConfigurations"] = typeof(bool), ["severityLevel"] = typeof(string) }; // Security patterns for detection private static readonly Dictionary SecurityPatterns = new() { // API Keys and Secrets ["github_token"] = (new Regex(@"ghp_[a-zA-Z0-9]{36}", RegexOptions.IgnoreCase), "Secret", "High", "GitHub Personal Access Token"), ["aws_access_key"] = (new Regex(@"AKIA[0-9A-Z]{16}", RegexOptions.IgnoreCase), "Secret", "Critical", "AWS Access Key ID"), ["aws_secret_key"] = (new Regex(@"[0-9a-zA-Z/+]{40}", RegexOptions.IgnoreCase), "Secret", "Critical", "Potential AWS Secret Access Key"), ["google_api_key"] = (new Regex(@"AIza[0-9A-Za-z\-_]{35}", RegexOptions.IgnoreCase), "Secret", "High", "Google API Key"), ["slack_token"] = (new Regex(@"xox[baprs]-([0-9a-zA-Z]{10,48})", RegexOptions.IgnoreCase), "Secret", "High", "Slack Token"), ["stripe_key"] = (new Regex(@"sk_live_[0-9a-zA-Z]{24}", RegexOptions.IgnoreCase), "Secret", "Critical", "Stripe Live Secret Key"), ["twilio_api_key"] = (new Regex(@"SK[0-9a-fA-F]{32}", RegexOptions.IgnoreCase), "Secret", "High", "Twilio API Key"), ["mailgun_api_key"] = (new Regex(@"key-[0-9a-zA-Z]{32}", RegexOptions.IgnoreCase), "Secret", "High", "Mailgun API Key"), ["jwt_secret"] = (new Regex(@"(jwt|token).{0,20}['""]?\s*[A-Za-z0-9+/=]{20,}", RegexOptions.IgnoreCase), "Secret", "High", "Potential JWT Secret"), // Generic secrets patterns ["password_hardcoded"] = (new Regex(@"(password|pwd|pass)\s*[=:]\s*['""]?\w{4,}['""]?", RegexOptions.IgnoreCase), "Secret", "High", "Hardcoded Password"), ["api_key_generic"] = (new Regex(@"(api.?key|apikey|api_key)\s*[=:]\s*['""]?\w{10,}['""]?", RegexOptions.IgnoreCase), "Secret", "High", "Hardcoded API Key"), ["secret_generic"] = (new Regex(@"(secret|token|auth)\s*[=:]\s*['""]?\w{10,}['""]?", RegexOptions.IgnoreCase), "Secret", "Medium", "Potential Hardcoded Secret"), // SQL Injection patterns ["sql_injection_concat"] = (new Regex(@"(SELECT|INSERT|UPDATE|DELETE).*([\+]|\|\|).*['""]", RegexOptions.IgnoreCase), "SQLInjection", "High", "SQL Query String Concatenation"), ["sql_injection_format"] = (new Regex(@"String\.Format.*(SELECT|INSERT|UPDATE|DELETE)", RegexOptions.IgnoreCase), "SQLInjection", "High", "SQL Query with String.Format"), ["sql_injection_interpolation"] = (new Regex(@"\$"".*?(SELECT|INSERT|UPDATE|DELETE).*?\{.*?\}", RegexOptions.IgnoreCase), "SQLInjection", "Medium", "SQL Query with String Interpolation"), // XSS patterns ["xss_innerhtml"] = (new Regex(@"\.innerHTML\s*=.*['""].*\+", RegexOptions.IgnoreCase), "XSS", "High", "Potential XSS via innerHTML"), ["xss_document_write"] = (new Regex(@"document\.write\(.*\+", RegexOptions.IgnoreCase), "XSS", "High", "Potential XSS via document.write"), ["xss_eval"] = (new Regex(@"eval\s*\(.*['""].*\+", RegexOptions.IgnoreCase), "XSS", "Critical", "Potential XSS via eval()"), // Insecure configurations ["debug_enabled"] = (new Regex(@"debug\s*[=:]\s*true", RegexOptions.IgnoreCase), "Configuration", "Medium", "Debug mode enabled"), ["ssl_disabled"] = (new Regex(@"(ssl|https)\s*[=:]\s*false", RegexOptions.IgnoreCase), "Configuration", "High", "SSL/HTTPS disabled"), ["weak_encryption"] = (new Regex(@"(MD5|SHA1|DES)\s*\(", RegexOptions.IgnoreCase), "Configuration", "Medium", "Weak encryption algorithm"), ["insecure_random"] = (new Regex(@"Random\s*\(\)", RegexOptions.IgnoreCase), "Configuration", "Low", "Potentially insecure random number generation"), // Authentication issues ["hardcoded_salt"] = (new Regex(@"salt\s*[=:]\s*['""]?\w+['""]?", RegexOptions.IgnoreCase), "Authentication", "High", "Hardcoded salt value"), ["weak_session_timeout"] = (new Regex(@"timeout\s*[=:]\s*['""]?\d{1,3}['""]?", RegexOptions.IgnoreCase), "Authentication", "Low", "Potentially weak session timeout") }; public async Task ExecuteAsync(IReadOnlyDictionary parameters) { // Properties are auto-populated by the AIPlugin system from AI's tool call try { // Validate required parameters if (string.IsNullOrEmpty(Path)) { return new AIPluginResult( new ArgumentException("Path is required"), "Path parameter is required" ); } string severityLevel = SeverityLevel ?? "medium"; // Validate severity level var validSeverities = new[] { "low", "medium", "high", "critical" }; if (!validSeverities.Contains(severityLevel.ToLower())) { return new AIPluginResult( new ArgumentException($"Invalid severity level: {severityLevel}. Must be one of: {string.Join(", ", validSeverities)}"), "Invalid severity level" ); } // Validate path exists if (!File.Exists(Path) && !Directory.Exists(Path)) { return new AIPluginResult( new FileNotFoundException($"Path not found: {Path}"), "Path not found" ); } // Collect files to scan var filesToScan = GetFilesToScan(Path); var results = new SecurityScanResults { ScannedPath = Path, FilesScanned = filesToScan.Count, SeverityLevel = severityLevel }; // Perform scans based on configuration foreach (var file in filesToScan) { var fileContent = await File.ReadAllTextAsync(file); var fileName = System.IO.Path.GetFileName(file); if (ScanSecrets) { await ScanForSecrets(file, fileContent, results); } if (ScanSqlInjection) { await ScanForSqlInjection(file, fileContent, results); } if (ScanXss) { await ScanForXss(file, fileContent, results); } if (ScanConfigurations) { await ScanForInsecureConfigurations(file, fileContent, results); } } // Filter results by severity level FilterResultsBySeverity(results, severityLevel); // Calculate overall risk score results.OverallRiskScore = CalculateRiskScore(results); results.OverallRisk = GetRiskLevel(results.OverallRiskScore); // Generate recommendations results.Recommendations = GenerateRecommendations(results); return new AIPluginResult(results, $"Security scan completed. Found {results.GetTotalIssues()} security issues."); } catch (Exception ex) { return new AIPluginResult(ex, $"Security scan failed: {ex.Message}"); } } /// /// Gets list of files to scan based on path /// private List GetFilesToScan(string path) { var files = new List(); if (File.Exists(path)) { files.Add(path); } else if (Directory.Exists(path)) { // Scan common code file types var extensions = new[] { "*.cs", "*.js", "*.ts", "*.py", "*.java", "*.php", "*.rb", "*.go", "*.cpp", "*.c", "*.h", "*.json", "*.xml", "*.config", "*.yml", "*.yaml", "*.properties", "*.ini" }; foreach (var extension in extensions) { files.AddRange(Directory.GetFiles(path, extension, SearchOption.AllDirectories)); } } // Filter out common excluded directories var excludedDirs = new[] { "node_modules", "bin", "obj", ".git", ".vs", "packages", "target", "build" }; files = files.Where(f => !excludedDirs.Any(dir => f.Contains($"{System.IO.Path.DirectorySeparatorChar}{dir}{System.IO.Path.DirectorySeparatorChar}"))).ToList(); return files; } /// /// Scans for hardcoded secrets and API keys /// private async Task ScanForSecrets(string filePath, string content, SecurityScanResults results) { var secretPatterns = SecurityPatterns.Where(p => p.Value.Type == "Secret").ToList(); foreach (var pattern in secretPatterns) { var matches = pattern.Value.Pattern.Matches(content); foreach (Match match in matches) { // Skip if it looks like a comment or example if (IsLikelyFalsePositive(content, match)) continue; results.Secrets.Add(new SecurityIssue { Type = "Secret", Severity = pattern.Value.Severity, Description = pattern.Value.Description, File = filePath, LineNumber = GetLineNumber(content, match.Index), Code = GetContextLines(content, match.Index, 1), Recommendation = GetSecretRecommendation(pattern.Key) }); } } } /// /// Scans for SQL injection vulnerabilities /// private async Task ScanForSqlInjection(string filePath, string content, SecurityScanResults results) { var sqlPatterns = SecurityPatterns.Where(p => p.Value.Type == "SQLInjection").ToList(); foreach (var pattern in sqlPatterns) { var matches = pattern.Value.Pattern.Matches(content); foreach (Match match in matches) { results.Vulnerabilities.Add(new SecurityIssue { Type = "SQL Injection", Severity = pattern.Value.Severity, Description = pattern.Value.Description, File = filePath, LineNumber = GetLineNumber(content, match.Index), Code = GetContextLines(content, match.Index, 2), Recommendation = "Use parameterized queries or an ORM to prevent SQL injection. Never concatenate user input directly into SQL queries." }); } } } /// /// Scans for XSS vulnerabilities /// private async Task ScanForXss(string filePath, string content, SecurityScanResults results) { var xssPatterns = SecurityPatterns.Where(p => p.Value.Type == "XSS").ToList(); foreach (var pattern in xssPatterns) { var matches = pattern.Value.Pattern.Matches(content); foreach (Match match in matches) { results.Vulnerabilities.Add(new SecurityIssue { Type = "Cross-Site Scripting (XSS)", Severity = pattern.Value.Severity, Description = pattern.Value.Description, File = filePath, LineNumber = GetLineNumber(content, match.Index), Code = GetContextLines(content, match.Index, 2), Recommendation = "Sanitize and encode all user input before displaying. Use safe DOM manipulation methods and avoid eval()." }); } } } /// /// Scans for insecure configurations /// private async Task ScanForInsecureConfigurations(string filePath, string content, SecurityScanResults results) { var configPatterns = SecurityPatterns.Where(p => p.Value.Type == "Configuration" || p.Value.Type == "Authentication").ToList(); foreach (var pattern in configPatterns) { var matches = pattern.Value.Pattern.Matches(content); foreach (Match match in matches) { results.ConfigurationIssues.Add(new SecurityIssue { Type = "Configuration", Severity = pattern.Value.Severity, Description = pattern.Value.Description, File = filePath, LineNumber = GetLineNumber(content, match.Index), Code = GetContextLines(content, match.Index, 1), Recommendation = GetConfigurationRecommendation(pattern.Key) }); } } } /// /// Checks if a match is likely a false positive (comment, example, etc.) /// private bool IsLikelyFalsePositive(string content, Match match) { var lineStart = content.LastIndexOf('\n', match.Index) + 1; var lineEnd = content.IndexOf('\n', match.Index); if (lineEnd == -1) lineEnd = content.Length; var line = content.Substring(lineStart, lineEnd - lineStart).Trim(); // Check for comments if (line.StartsWith("//") || line.StartsWith("#") || line.StartsWith("/*") || line.Contains("