MarketAlly.AIPlugin.Extensions/MarketAlly.AIPlugin.Security/VulnerabilityAnalyzerPlugin.cs

751 lines
23 KiB
C#
Executable File

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
{
/// <summary>
/// Analyzes project dependencies for known vulnerabilities and outdated packages
/// </summary>
[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<string, Type> SupportedParameters => new Dictionary<string, Type>
{
["projectPath"] = typeof(string),
["includeTransitive"] = typeof(bool),
["checkOutdated"] = typeof(bool),
["vulnerabilitySource"] = typeof(string),
["generateRecommendations"] = typeof(bool)
};
public async Task<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> 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}");
}
}
/// <summary>
/// Finds supported project files in the given path
/// </summary>
private List<string> FindProjectFiles(string path)
{
var projectFiles = new List<string>();
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;
}
/// <summary>
/// Checks if a file is a supported project file
/// </summary>
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);
}
/// <summary>
/// Analyzes a single project file for dependencies
/// </summary>
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}");
}
}
/// <summary>
/// Analyzes .NET project files (csproj, vbproj, fsproj)
/// </summary>
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);
}
}
/// <summary>
/// Analyzes Node.js package.json files
/// </summary>
private async Task AnalyzeNodeProject(string projectFile, VulnerabilityAnalysisResults results, bool includeTransitive, bool checkOutdated)
{
var content = await File.ReadAllTextAsync(projectFile);
var packageJson = JsonSerializer.Deserialize<JsonElement>(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);
}
}
/// <summary>
/// Analyzes Python requirements.txt files
/// </summary>
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);
}
}
}
/// <summary>
/// Analyzes Python Pipfile
/// </summary>
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);
}
}
}
}
/// <summary>
/// Analyzes Java Maven pom.xml files
/// </summary>
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);
}
}
}
/// <summary>
/// Placeholder for analyzing .NET transitive dependencies
/// </summary>
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
}
/// <summary>
/// Placeholder for analyzing Node.js transitive dependencies
/// </summary>
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
}
/// <summary>
/// Checks discovered packages against vulnerability databases
/// </summary>
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);
}
}
/// <summary>
/// Queries vulnerability database for a specific package
/// </summary>
private async Task<List<Vulnerability>> QueryVulnerabilityDatabase(PackageDependency dependency, string source)
{
var vulnerabilities = new List<Vulnerability>();
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;
}
/// <summary>
/// Mock NVD vulnerability query
/// </summary>
private async Task<List<Vulnerability>> 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<string, List<Vulnerability>>
{
["lodash"] = new List<Vulnerability>
{
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<Vulnerability>
{
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<Vulnerability>());
}
/// <summary>
/// Mock GitHub Advisory query
/// </summary>
private async Task<List<Vulnerability>> QueryGitHubAdvisory(PackageDependency dependency)
{
// Mock implementation - in reality would call GitHub Security Advisory API
return new List<Vulnerability>();
}
/// <summary>
/// Mock Snyk query
/// </summary>
private async Task<List<Vulnerability>> QuerySnyk(PackageDependency dependency)
{
// Mock implementation - in reality would call Snyk API
return new List<Vulnerability>();
}
/// <summary>
/// Checks if a package is outdated (simplified)
/// </summary>
private async Task<bool> 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());
}
/// <summary>
/// Gets the latest version of a package (simplified)
/// </summary>
private async Task<string> GetLatestVersion(PackageDependency dependency)
{
// Mock implementation
var mockLatestVersions = new Dictionary<string, string>
{
["lodash"] = "4.17.21",
["jquery"] = "3.6.0",
["express"] = "4.18.2",
["react"] = "18.2.0"
};
return mockLatestVersions.GetValueOrDefault(dependency.Name.ToLower(), "unknown");
}
/// <summary>
/// Calculates overall risk score based on vulnerabilities
/// </summary>
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;
}
/// <summary>
/// Generates upgrade and security recommendations
/// </summary>
private List<string> GenerateRecommendationsMethod(VulnerabilityAnalysisResults results)
{
var recommendations = new List<string>();
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;
}
}
/// <summary>
/// Results of vulnerability analysis
/// </summary>
public class VulnerabilityAnalysisResults
{
public string ProjectPath { get; set; }
public int ProjectFilesAnalyzed { get; set; }
public string VulnerabilitySource { get; set; }
public List<PackageDependency> Dependencies { get; set; } = new List<PackageDependency>();
public List<VulnerablePackage> VulnerablePackages { get; set; } = new List<VulnerablePackage>();
public List<OutdatedPackage> OutdatedPackages { get; set; } = new List<OutdatedPackage>();
public List<string> Recommendations { get; set; } = new List<string>();
public List<string> AnalysisErrors { get; set; } = new List<string>();
public int RiskScore { get; set; }
}
/// <summary>
/// Represents a package dependency
/// </summary>
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; }
}
/// <summary>
/// Represents a vulnerable package
/// </summary>
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<Vulnerability> Vulnerabilities { get; set; } = new List<Vulnerability>();
}
/// <summary>
/// Represents an outdated package
/// </summary>
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; }
}
/// <summary>
/// Represents a security vulnerability
/// </summary>
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; }
}
}