using MarketAlly.AIPlugin; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml.Linq; namespace MarketAlly.AIPlugin.Security.Plugins { /// /// Analyzes project dependencies for known vulnerabilities and outdated packages /// [AIPlugin("VulnerabilityAnalyzer", "Analyzes project dependencies for known vulnerabilities and outdated packages")] public class VulnerabilityAnalyzerPlugin : IAIPlugin { private readonly HttpClient _httpClient; public VulnerabilityAnalyzerPlugin(HttpClient httpClient = null) { _httpClient = httpClient ?? new HttpClient(); } [AIParameter("Full path to the project file or directory", required: true)] public string ProjectPath { get; set; } [AIParameter("Include transitive dependencies in analysis", required: false)] public bool IncludeTransitive { get; set; } = true; [AIParameter("Check for outdated packages", required: false)] public bool CheckOutdated { get; set; } = true; [AIParameter("Vulnerability database source: nvd, github, snyk", required: false)] public string VulnerabilitySource { get; set; } = "nvd"; [AIParameter("Generate upgrade recommendations", required: false)] public bool GenerateRecommendations { get; set; } = true; public IReadOnlyDictionary SupportedParameters => new Dictionary { ["projectPath"] = typeof(string), ["includeTransitive"] = typeof(bool), ["checkOutdated"] = typeof(bool), ["vulnerabilitySource"] = typeof(string), ["generateRecommendations"] = typeof(bool) }; public async Task ExecuteAsync(IReadOnlyDictionary parameters) { try { // Extract parameters string projectPath = parameters["projectPath"].ToString(); bool includeTransitive = parameters.TryGetValue("includeTransitive", out var transObj) ? Convert.ToBoolean(transObj) : true; bool checkOutdated = parameters.TryGetValue("checkOutdated", out var outdatedObj) ? Convert.ToBoolean(outdatedObj) : true; string vulnSource = parameters.TryGetValue("vulnerabilitySource", out var sourceObj) ? sourceObj.ToString() : "nvd"; bool generateRecommendations = parameters.TryGetValue("generateRecommendations", out var recObj) ? Convert.ToBoolean(recObj) : true; // Validate vulnerability source var validSources = new[] { "nvd", "github", "snyk" }; if (!validSources.Contains(vulnSource.ToLower())) { return new AIPluginResult( new ArgumentException($"Invalid vulnerability source: {vulnSource}. Must be one of: {string.Join(", ", validSources)}"), "Invalid vulnerability source" ); } // Find project files to analyze var projectFiles = FindProjectFiles(projectPath); if (!projectFiles.Any()) { return new AIPluginResult( new FileNotFoundException($"No supported project files found in: {projectPath}"), "No project files found" ); } var results = new VulnerabilityAnalysisResults { ProjectPath = projectPath, VulnerabilitySource = vulnSource, ProjectFilesAnalyzed = projectFiles.Count }; // Analyze each project file foreach (var projectFile in projectFiles) { await AnalyzeProjectFile(projectFile, results, includeTransitive, checkOutdated); } // Check vulnerabilities for discovered packages if (results.Dependencies.Any()) { await CheckVulnerabilities(results, vulnSource); } // Generate recommendations if requested if (generateRecommendations) { results.Recommendations = GenerateRecommendationsMethod(results); } // Calculate overall risk score results.RiskScore = CalculateRiskScore(results); return new AIPluginResult(results, $"Vulnerability analysis completed. Found {results.VulnerablePackages.Count} vulnerable packages and {results.OutdatedPackages.Count} outdated packages."); } catch (Exception ex) { return new AIPluginResult(ex, $"Vulnerability analysis failed: {ex.Message}"); } } /// /// Finds supported project files in the given path /// private List FindProjectFiles(string path) { var projectFiles = new List(); if (File.Exists(path)) { if (IsSupportedProjectFile(path)) { projectFiles.Add(path); } } else if (Directory.Exists(path)) { // Look for various project file types var patterns = new[] { "*.csproj", "*.vbproj", "*.fsproj", // .NET "package.json", // Node.js "requirements.txt", "Pipfile", "pyproject.toml", // Python "pom.xml", "build.gradle", // Java "Gemfile", // Ruby "composer.json", // PHP "Cargo.toml", // Rust "go.mod" // Go }; foreach (var pattern in patterns) { projectFiles.AddRange(Directory.GetFiles(path, pattern, SearchOption.AllDirectories)); } } return projectFiles; } /// /// Checks if a file is a supported project file /// private bool IsSupportedProjectFile(string filePath) { var fileName = Path.GetFileName(filePath).ToLower(); var supportedFiles = new[] { "package.json", "requirements.txt", "pipfile", "pyproject.toml", "pom.xml", "build.gradle", "gemfile", "composer.json", "cargo.toml", "go.mod" }; return fileName.EndsWith(".csproj") || fileName.EndsWith(".vbproj") || fileName.EndsWith(".fsproj") || supportedFiles.Contains(fileName); } /// /// Analyzes a single project file for dependencies /// private async Task AnalyzeProjectFile(string projectFile, VulnerabilityAnalysisResults results, bool includeTransitive, bool checkOutdated) { var fileName = Path.GetFileName(projectFile).ToLower(); try { if (fileName.EndsWith(".csproj") || fileName.EndsWith(".vbproj") || fileName.EndsWith(".fsproj")) { await AnalyzeDotNetProject(projectFile, results, includeTransitive, checkOutdated); } else if (fileName == "package.json") { await AnalyzeNodeProject(projectFile, results, includeTransitive, checkOutdated); } else if (fileName == "requirements.txt") { await AnalyzePythonRequirements(projectFile, results, checkOutdated); } else if (fileName == "pipfile") { await AnalyzePipfile(projectFile, results, checkOutdated); } else if (fileName == "pom.xml") { await AnalyzeJavaMaven(projectFile, results, checkOutdated); } // Add more project type handlers as needed } catch (Exception ex) { results.AnalysisErrors.Add($"Error analyzing {projectFile}: {ex.Message}"); } } /// /// Analyzes .NET project files (csproj, vbproj, fsproj) /// private async Task AnalyzeDotNetProject(string projectFile, VulnerabilityAnalysisResults results, bool includeTransitive, bool checkOutdated) { var content = await File.ReadAllTextAsync(projectFile); var doc = XDocument.Parse(content); // Extract PackageReference elements var packageRefs = doc.Descendants("PackageReference"); foreach (var packageRef in packageRefs) { var name = packageRef.Attribute("Include")?.Value; var version = packageRef.Attribute("Version")?.Value; if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(version)) { var dependency = new PackageDependency { Name = name, CurrentVersion = version, Ecosystem = "NuGet", ProjectFile = projectFile, IsTransitive = false }; results.Dependencies.Add(dependency); } } // TODO: For transitive dependencies, would need to parse packages.lock.json or use NuGet APIs if (includeTransitive) { await AnalyzeDotNetTransitiveDependencies(projectFile, results); } } /// /// Analyzes Node.js package.json files /// private async Task AnalyzeNodeProject(string projectFile, VulnerabilityAnalysisResults results, bool includeTransitive, bool checkOutdated) { var content = await File.ReadAllTextAsync(projectFile); var packageJson = JsonSerializer.Deserialize(content); // Analyze dependencies and devDependencies var depSections = new[] { "dependencies", "devDependencies" }; foreach (var section in depSections) { if (packageJson.TryGetProperty(section, out var deps)) { foreach (var dep in deps.EnumerateObject()) { var dependency = new PackageDependency { Name = dep.Name, CurrentVersion = dep.Value.GetString(), Ecosystem = "npm", ProjectFile = projectFile, IsTransitive = false, IsDevelopmentDependency = section == "devDependencies" }; results.Dependencies.Add(dependency); } } } // For transitive dependencies, would need to parse package-lock.json or node_modules if (includeTransitive) { await AnalyzeNodeTransitiveDependencies(projectFile, results); } } /// /// Analyzes Python requirements.txt files /// private async Task AnalyzePythonRequirements(string projectFile, VulnerabilityAnalysisResults results, bool checkOutdated) { var lines = await File.ReadAllLinesAsync(projectFile); var versionRegex = new Regex(@"^([a-zA-Z0-9\-_\.]+)([>=<~!]+)(.+)$"); foreach (var line in lines) { var trimmedLine = line.Trim(); if (string.IsNullOrEmpty(trimmedLine) || trimmedLine.StartsWith("#")) continue; var match = versionRegex.Match(trimmedLine); if (match.Success) { var dependency = new PackageDependency { Name = match.Groups[1].Value, CurrentVersion = match.Groups[3].Value, Ecosystem = "PyPI", ProjectFile = projectFile, IsTransitive = false, VersionConstraint = match.Groups[2].Value }; results.Dependencies.Add(dependency); } } } /// /// Analyzes Python Pipfile /// private async Task AnalyzePipfile(string projectFile, VulnerabilityAnalysisResults results, bool checkOutdated) { // Simple TOML-like parsing for Pipfile var content = await File.ReadAllTextAsync(projectFile); var lines = content.Split('\n'); bool inPackagesSection = false; bool inDevPackagesSection = false; foreach (var line in lines) { var trimmedLine = line.Trim(); if (trimmedLine == "[packages]") { inPackagesSection = true; inDevPackagesSection = false; continue; } else if (trimmedLine == "[dev-packages]") { inDevPackagesSection = true; inPackagesSection = false; continue; } else if (trimmedLine.StartsWith("[")) { inPackagesSection = false; inDevPackagesSection = false; continue; } if ((inPackagesSection || inDevPackagesSection) && trimmedLine.Contains("=")) { var parts = trimmedLine.Split('=', 2); if (parts.Length == 2) { var name = parts[0].Trim(); var version = parts[1].Trim().Trim('"', '\''); var dependency = new PackageDependency { Name = name, CurrentVersion = version, Ecosystem = "PyPI", ProjectFile = projectFile, IsTransitive = false, IsDevelopmentDependency = inDevPackagesSection }; results.Dependencies.Add(dependency); } } } } /// /// Analyzes Java Maven pom.xml files /// private async Task AnalyzeJavaMaven(string projectFile, VulnerabilityAnalysisResults results, bool checkOutdated) { var content = await File.ReadAllTextAsync(projectFile); var doc = XDocument.Parse(content); var ns = doc.Root?.GetDefaultNamespace(); // Extract dependency elements var dependencies = doc.Descendants(ns + "dependency"); foreach (var dep in dependencies) { var groupId = dep.Element(ns + "groupId")?.Value; var artifactId = dep.Element(ns + "artifactId")?.Value; var version = dep.Element(ns + "version")?.Value; var scope = dep.Element(ns + "scope")?.Value ?? "compile"; if (!string.IsNullOrEmpty(groupId) && !string.IsNullOrEmpty(artifactId) && !string.IsNullOrEmpty(version)) { var dependency = new PackageDependency { Name = $"{groupId}:{artifactId}", CurrentVersion = version, Ecosystem = "Maven", ProjectFile = projectFile, IsTransitive = false, IsDevelopmentDependency = scope == "test" }; results.Dependencies.Add(dependency); } } } /// /// Placeholder for analyzing .NET transitive dependencies /// private async Task AnalyzeDotNetTransitiveDependencies(string projectFile, VulnerabilityAnalysisResults results) { // In a real implementation, this would parse packages.lock.json or use NuGet APIs // For now, we'll skip transitive dependency analysis } /// /// Placeholder for analyzing Node.js transitive dependencies /// private async Task AnalyzeNodeTransitiveDependencies(string projectFile, VulnerabilityAnalysisResults results) { // In a real implementation, this would parse package-lock.json or node_modules // For now, we'll skip transitive dependency analysis } /// /// Checks discovered packages against vulnerability databases /// private async Task CheckVulnerabilities(VulnerabilityAnalysisResults results, string source) { foreach (var dependency in results.Dependencies) { try { var vulnerabilities = await QueryVulnerabilityDatabase(dependency, source); if (vulnerabilities.Any()) { var vulnerablePackage = new VulnerablePackage { Name = dependency.Name, CurrentVersion = dependency.CurrentVersion, Ecosystem = dependency.Ecosystem, ProjectFile = dependency.ProjectFile, Vulnerabilities = vulnerabilities }; results.VulnerablePackages.Add(vulnerablePackage); } // Check if package is outdated (simplified check) if (await IsPackageOutdated(dependency)) { var outdatedPackage = new OutdatedPackage { Name = dependency.Name, CurrentVersion = dependency.CurrentVersion, LatestVersion = await GetLatestVersion(dependency), Ecosystem = dependency.Ecosystem, ProjectFile = dependency.ProjectFile }; results.OutdatedPackages.Add(outdatedPackage); } } catch (Exception ex) { results.AnalysisErrors.Add($"Error checking {dependency.Name}: {ex.Message}"); } // Add delay to avoid rate limiting await Task.Delay(100); } } /// /// Queries vulnerability database for a specific package /// private async Task> QueryVulnerabilityDatabase(PackageDependency dependency, string source) { var vulnerabilities = new List(); try { // This is a simplified mock implementation // In a real implementation, you would call actual vulnerability APIs switch (source.ToLower()) { case "nvd": vulnerabilities.AddRange(await QueryNVD(dependency)); break; case "github": vulnerabilities.AddRange(await QueryGitHubAdvisory(dependency)); break; case "snyk": vulnerabilities.AddRange(await QuerySnyk(dependency)); break; } } catch (Exception ex) { // Log error but don't fail the entire analysis Console.WriteLine($"Error querying vulnerability database for {dependency.Name}: {ex.Message}"); } return vulnerabilities; } /// /// Mock NVD vulnerability query /// private async Task> QueryNVD(PackageDependency dependency) { // In a real implementation, this would call the NVD API // For demo purposes, we'll return some mock vulnerabilities for common packages var mockVulnerabilities = new Dictionary> { ["lodash"] = new List { new Vulnerability { Id = "CVE-2021-23337", Severity = "High", Description = "Lodash versions prior to 4.17.21 are vulnerable to Command Injection via template.", AffectedVersions = "< 4.17.21", FixedVersion = "4.17.21", PublishedDate = DateTime.Parse("2021-02-15"), CvssScore = 7.2 } }, ["jquery"] = new List { new Vulnerability { Id = "CVE-2020-11022", Severity = "Medium", Description = "In jQuery versions greater than or equal to 1.2 and before 3.5.0, passing HTML from untrusted sources could result in XSS.", AffectedVersions = ">= 1.2, < 3.5.0", FixedVersion = "3.5.0", PublishedDate = DateTime.Parse("2020-04-29"), CvssScore = 6.1 } } }; return mockVulnerabilities.GetValueOrDefault(dependency.Name.ToLower(), new List()); } /// /// Mock GitHub Advisory query /// private async Task> QueryGitHubAdvisory(PackageDependency dependency) { // Mock implementation - in reality would call GitHub Security Advisory API return new List(); } /// /// Mock Snyk query /// private async Task> QuerySnyk(PackageDependency dependency) { // Mock implementation - in reality would call Snyk API return new List(); } /// /// Checks if a package is outdated (simplified) /// private async Task IsPackageOutdated(PackageDependency dependency) { // Simplified mock implementation // In reality, would check against package registries var outdatedPackages = new[] { "lodash", "jquery", "express", "react" }; return outdatedPackages.Contains(dependency.Name.ToLower()); } /// /// Gets the latest version of a package (simplified) /// private async Task GetLatestVersion(PackageDependency dependency) { // Mock implementation var mockLatestVersions = new Dictionary { ["lodash"] = "4.17.21", ["jquery"] = "3.6.0", ["express"] = "4.18.2", ["react"] = "18.2.0" }; return mockLatestVersions.GetValueOrDefault(dependency.Name.ToLower(), "unknown"); } /// /// Calculates overall risk score based on vulnerabilities /// private int CalculateRiskScore(VulnerabilityAnalysisResults results) { var score = 0; foreach (var vulnPackage in results.VulnerablePackages) { foreach (var vuln in vulnPackage.Vulnerabilities) { score += vuln.Severity.ToLower() switch { "critical" => 10, "high" => 7, "medium" => 4, "low" => 1, _ => 2 }; } } // Add points for outdated packages score += results.OutdatedPackages.Count * 2; return score; } /// /// Generates upgrade and security recommendations /// private List GenerateRecommendationsMethod(VulnerabilityAnalysisResults results) { var recommendations = new List(); if (results.VulnerablePackages.Any()) { recommendations.Add("🚨 **Immediate Action Required**: Update vulnerable packages to secure versions."); var criticalVulns = results.VulnerablePackages .SelectMany(p => p.Vulnerabilities) .Where(v => v.Severity.ToLower() == "critical") .Count(); if (criticalVulns > 0) { recommendations.Add($"⚠️ **Critical**: {criticalVulns} critical vulnerabilities found. Update immediately."); } // Specific package recommendations foreach (var vulnPackage in results.VulnerablePackages.Take(5)) { var highestSeverityVuln = vulnPackage.Vulnerabilities .OrderByDescending(v => v.CvssScore) .First(); if (!string.IsNullOrEmpty(highestSeverityVuln.FixedVersion)) { recommendations.Add($"📦 Update {vulnPackage.Name} from {vulnPackage.CurrentVersion} to {highestSeverityVuln.FixedVersion} or later"); } } } if (results.OutdatedPackages.Any()) { recommendations.Add($"📅 {results.OutdatedPackages.Count} packages are outdated. Consider updating to latest versions."); recommendations.Add("🔄 Set up automated dependency updates (Dependabot, Renovate, etc.)"); } // General security recommendations recommendations.Add("🛡️ Enable security alerts in your repository settings"); recommendations.Add("🔍 Run dependency scans regularly in your CI/CD pipeline"); recommendations.Add("📋 Consider using a Software Bill of Materials (SBOM) for tracking"); if (results.VulnerablePackages.Count == 0 && results.OutdatedPackages.Count == 0) { recommendations.Add("✅ Excellent! No known vulnerabilities or severely outdated packages found."); recommendations.Add("🔒 Continue monitoring dependencies regularly for new vulnerabilities."); } return recommendations; } } /// /// Results of vulnerability analysis /// public class VulnerabilityAnalysisResults { public string ProjectPath { get; set; } public int ProjectFilesAnalyzed { get; set; } public string VulnerabilitySource { get; set; } public List Dependencies { get; set; } = new List(); public List VulnerablePackages { get; set; } = new List(); public List OutdatedPackages { get; set; } = new List(); public List Recommendations { get; set; } = new List(); public List AnalysisErrors { get; set; } = new List(); public int RiskScore { get; set; } } /// /// Represents a package dependency /// public class PackageDependency { public string Name { get; set; } public string CurrentVersion { get; set; } public string Ecosystem { get; set; } public string ProjectFile { get; set; } public bool IsTransitive { get; set; } public bool IsDevelopmentDependency { get; set; } public string VersionConstraint { get; set; } } /// /// Represents a vulnerable package /// public class VulnerablePackage { public string Name { get; set; } public string CurrentVersion { get; set; } public string Ecosystem { get; set; } public string ProjectFile { get; set; } public List Vulnerabilities { get; set; } = new List(); } /// /// Represents an outdated package /// public class OutdatedPackage { public string Name { get; set; } public string CurrentVersion { get; set; } public string LatestVersion { get; set; } public string Ecosystem { get; set; } public string ProjectFile { get; set; } } /// /// Represents a security vulnerability /// public class Vulnerability { public string Id { get; set; } public string Severity { get; set; } public string Description { get; set; } public string AffectedVersions { get; set; } public string FixedVersion { get; set; } public DateTime PublishedDate { get; set; } public double CvssScore { get; set; } } }