MarketAlly.AIPlugin.Extensions/MarketAlly.AIPlugin.Security/SecurityScanPlugin.cs

509 lines
20 KiB
C#
Executable File

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
{
/// <summary>
/// Comprehensive security analysis including hardcoded secrets, vulnerabilities, and security patterns
/// </summary>
[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<string, Type> SupportedParameters => new Dictionary<string, Type>
{
["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<string, (Regex Pattern, string Type, string Severity, string Description)> 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<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> 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}");
}
}
/// <summary>
/// Gets list of files to scan based on path
/// </summary>
private List<string> GetFilesToScan(string path)
{
var files = new List<string>();
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;
}
/// <summary>
/// Scans for hardcoded secrets and API keys
/// </summary>
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)
});
}
}
}
/// <summary>
/// Scans for SQL injection vulnerabilities
/// </summary>
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."
});
}
}
}
/// <summary>
/// Scans for XSS vulnerabilities
/// </summary>
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()."
});
}
}
}
/// <summary>
/// Scans for insecure configurations
/// </summary>
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)
});
}
}
}
/// <summary>
/// Checks if a match is likely a false positive (comment, example, etc.)
/// </summary>
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("<!-- "))
return true;
// Check for common example/placeholder patterns
var falsePositivePatterns = new[] { "example", "placeholder", "your_api_key", "your_secret", "xxxxxxx", "123456", "test", "demo" };
return falsePositivePatterns.Any(pattern => match.Value.ToLower().Contains(pattern));
}
/// <summary>
/// Gets the line number for a character index in content
/// </summary>
private int GetLineNumber(string content, int index)
{
return content.Take(index).Count(c => c == '\n') + 1;
}
/// <summary>
/// Gets context lines around a match
/// </summary>
private string GetContextLines(string content, int index, int contextLines)
{
var lines = content.Split('\n');
var targetLine = GetLineNumber(content, index) - 1;
var startLine = Math.Max(0, targetLine - contextLines);
var endLine = Math.Min(lines.Length - 1, targetLine + contextLines);
var contextList = new List<string>();
for (int i = startLine; i <= endLine; i++)
{
var marker = i == targetLine ? ">>> " : " ";
contextList.Add($"{marker}{i + 1}: {lines[i]}");
}
return string.Join("\n", contextList);
}
/// <summary>
/// Gets recommendation for a specific secret type
/// </summary>
private string GetSecretRecommendation(string secretType)
{
return secretType switch
{
"github_token" => "Move GitHub tokens to environment variables or use GitHub Secrets for CI/CD.",
"aws_access_key" or "aws_secret_key" => "Use AWS IAM roles, environment variables, or AWS Secrets Manager.",
"google_api_key" => "Store Google API keys in environment variables or Google Secret Manager.",
"stripe_key" => "Never commit Stripe keys to source control. Use environment variables and test keys for development.",
"jwt_secret" => "Use a strong, randomly generated JWT secret stored in environment variables.",
_ => "Move all secrets to environment variables, configuration files outside source control, or a secure key management system."
};
}
/// <summary>
/// Gets recommendation for configuration issues
/// </summary>
private string GetConfigurationRecommendation(string configType)
{
return configType switch
{
"debug_enabled" => "Disable debug mode in production environments.",
"ssl_disabled" => "Always enable SSL/HTTPS in production environments.",
"weak_encryption" => "Use strong encryption algorithms like AES-256, SHA-256, or better.",
"insecure_random" => "Use cryptographically secure random number generators for security-sensitive operations.",
"hardcoded_salt" => "Generate unique, random salts for each password hash.",
"weak_session_timeout" => "Configure appropriate session timeouts based on your security requirements.",
_ => "Review and secure this configuration according to security best practices."
};
}
/// <summary>
/// Filters results based on minimum severity level
/// </summary>
private void FilterResultsBySeverity(SecurityScanResults results, string minSeverityLevel)
{
var severityOrder = new Dictionary<string, int> { ["low"] = 1, ["medium"] = 2, ["high"] = 3, ["critical"] = 4 };
var minLevel = severityOrder[minSeverityLevel.ToLower()];
results.Secrets = results.Secrets.Where(s => severityOrder[s.Severity.ToLower()] >= minLevel).ToList();
results.Vulnerabilities = results.Vulnerabilities.Where(v => severityOrder[v.Severity.ToLower()] >= minLevel).ToList();
results.ConfigurationIssues = results.ConfigurationIssues.Where(c => severityOrder[c.Severity.ToLower()] >= minLevel).ToList();
}
/// <summary>
/// Calculates overall risk score based on findings
/// </summary>
private int CalculateRiskScore(SecurityScanResults results)
{
var score = 0;
var severityWeights = new Dictionary<string, int> { ["low"] = 1, ["medium"] = 3, ["high"] = 7, ["critical"] = 15 };
foreach (var issue in results.Secrets.Concat(results.Vulnerabilities).Concat(results.ConfigurationIssues))
{
score += severityWeights[issue.Severity.ToLower()];
}
return score;
}
/// <summary>
/// Gets risk level based on score
/// </summary>
private string GetRiskLevel(int score)
{
return score switch
{
0 => "Very Low",
<= 5 => "Low",
<= 15 => "Medium",
<= 30 => "High",
_ => "Critical"
};
}
/// <summary>
/// Generates security recommendations based on findings
/// </summary>
private List<string> GenerateRecommendations(SecurityScanResults results)
{
var recommendations = new List<string>();
if (results.Secrets.Any())
{
recommendations.Add("🔑 Implement a secure secrets management strategy using environment variables, Azure Key Vault, AWS Secrets Manager, or similar services.");
recommendations.Add("📋 Audit your codebase regularly for hardcoded secrets using automated tools.");
}
if (results.Vulnerabilities.Any(v => v.Type.Contains("SQL")))
{
recommendations.Add("🛡️ Use parameterized queries, stored procedures, or ORM frameworks to prevent SQL injection attacks.");
}
if (results.Vulnerabilities.Any(v => v.Type.Contains("XSS")))
{
recommendations.Add("🧹 Implement proper input validation and output encoding to prevent XSS attacks.");
recommendations.Add("🔒 Use Content Security Policy (CSP) headers to mitigate XSS risks.");
}
if (results.ConfigurationIssues.Any())
{
recommendations.Add("⚙️ Review and harden all configuration settings for production environments.");
recommendations.Add("🔐 Use strong encryption algorithms and secure authentication mechanisms.");
}
if (results.GetTotalIssues() == 0)
{
recommendations.Add("✅ Great! No major security issues found. Continue following security best practices.");
recommendations.Add("🔄 Consider running security scans regularly as part of your CI/CD pipeline.");
}
return recommendations;
}
}
/// <summary>
/// Represents the results of a security scan
/// </summary>
public class SecurityScanResults
{
public string ScannedPath { get; set; }
public int FilesScanned { get; set; }
public string SeverityLevel { get; set; }
public List<SecurityIssue> Secrets { get; set; } = new List<SecurityIssue>();
public List<SecurityIssue> Vulnerabilities { get; set; } = new List<SecurityIssue>();
public List<SecurityIssue> ConfigurationIssues { get; set; } = new List<SecurityIssue>();
public List<string> Recommendations { get; set; } = new List<string>();
public int OverallRiskScore { get; set; }
public string OverallRisk { get; set; }
public int GetTotalIssues() => Secrets.Count + Vulnerabilities.Count + ConfigurationIssues.Count;
}
/// <summary>
/// Represents a single security issue
/// </summary>
public class SecurityIssue
{
public string Type { get; set; }
public string Severity { get; set; }
public string Description { get; set; }
public string File { get; set; }
public int LineNumber { get; set; }
public string Code { get; set; }
public string Recommendation { get; set; }
}
}