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("