smartui-migration-tool
Version:
Enterprise-grade CLI tool for migrating visual testing platforms to LambdaTest SmartUI
858 lines (743 loc) ⢠28.2 kB
JavaScript
const { Command, Flags } = require('@oclif/core');
const chalk = require('chalk');
const fs = require('fs-extra');
const path = require('path');
const glob = require('glob');
class SecurityScanner extends Command {
static description = 'Advanced security scanner with vulnerability detection, compliance checking, and threat analysis';
static flags = {
path: Flags.string({
char: 'p',
description: 'Path to scan (default: current directory)',
default: process.cwd()
}),
include: Flags.string({
char: 'i',
description: 'File patterns to include (comma-separated)',
default: '**/*.{js,ts,jsx,tsx,py,java,cs,json,yaml,yml}'
}),
exclude: Flags.string({
char: 'e',
description: 'File patterns to exclude (comma-separated)',
default: 'node_modules/**,dist/**,build/**,*.min.js'
}),
scan: Flags.string({
char: 's',
description: 'Type of security scan to perform',
options: ['vulnerabilities', 'secrets', 'dependencies', 'compliance', 'threats', 'all'],
default: 'all'
}),
severity: Flags.string({
char: 'S',
description: 'Minimum severity level to report',
options: ['low', 'medium', 'high', 'critical'],
default: 'medium'
}),
compliance: Flags.string({
char: 'c',
description: 'Compliance standard to check against',
options: ['owasp', 'pci-dss', 'sox', 'gdpr', 'hipaa', 'all'],
default: 'all'
}),
output: Flags.string({
char: 'o',
description: 'Output file for security scan results',
default: 'security-scan.json'
}),
format: Flags.string({
char: 'f',
description: 'Output format for results',
options: ['json', 'html', 'sarif', 'junit', 'csv'],
default: 'json'
}),
fix: Flags.boolean({
char: 'F',
description: 'Automatically fix low-risk issues',
default: false
}),
baseline: Flags.string({
char: 'b',
description: 'Baseline file for comparison',
default: ''
}),
verbose: Flags.boolean({
char: 'v',
description: 'Enable verbose output',
default: false
})
};
async run() {
const { flags } = await this.parse(SecurityScanner);
console.log(chalk.blue.bold('\nš Security Scanner'));
console.log(chalk.gray(`Performing ${flags.scan} security scan with ${flags.compliance} compliance...\n`));
try {
// Create security scanner
const scanner = this.createSecurityScanner(flags.compliance);
// Find files to scan
const files = await this.findFiles(flags);
// Perform security scan
const results = await this.performSecurityScan(files, flags, scanner);
// Display results
this.displayResults(results, flags.verbose);
// Save results
await this.saveResults(results, flags.output, flags.format);
console.log(chalk.green(`\nā
Security scan results saved to: ${flags.output}`));
// Apply fixes if requested
if (flags.fix) {
await this.applyFixes(results, flags);
}
} catch (error) {
console.error(chalk.red(`\nā Error during security scan: ${error.message}`));
this.exit(1);
}
}
createSecurityScanner(compliance) {
return {
// Vulnerability Scanning
scanVulnerabilities: async (files) => {
const vulnerabilities = [];
for (const file of files) {
try {
const content = await fs.readFile(file.path, 'utf8');
const fileVulns = this.detectVulnerabilities(content, file.path);
vulnerabilities.push(...fileVulns);
} catch (error) {
// Skip files that can't be read
}
}
return vulnerabilities;
},
// Secret Detection
scanSecrets: async (files) => {
const secrets = [];
for (const file of files) {
try {
const content = await fs.readFile(file.path, 'utf8');
const fileSecrets = this.detectSecrets(content, file.path);
secrets.push(...fileSecrets);
} catch (error) {
// Skip files that can't be read
}
}
return secrets;
},
// Dependency Scanning
scanDependencies: async (files) => {
const dependencies = [];
for (const file of files) {
if (file.path.endsWith('package.json') || file.path.endsWith('requirements.txt') ||
file.path.endsWith('pom.xml') || file.path.endsWith('.csproj')) {
try {
const content = await fs.readFile(file.path, 'utf8');
const fileDeps = this.analyzeDependencies(content, file.path);
dependencies.push(...fileDeps);
} catch (error) {
// Skip files that can't be read
}
}
}
return dependencies;
},
// Compliance Checking
checkCompliance: async (files, compliance) => {
const complianceIssues = [];
for (const file of files) {
try {
const content = await fs.readFile(file.path, 'utf8');
const issues = this.checkComplianceRules(content, file.path, compliance);
complianceIssues.push(...issues);
} catch (error) {
// Skip files that can't be read
}
}
return complianceIssues;
},
// Threat Analysis
analyzeThreats: async (files) => {
const threats = [];
for (const file of files) {
try {
const content = await fs.readFile(file.path, 'utf8');
const fileThreats = this.analyzeThreats(content, file.path);
threats.push(...fileThreats);
} catch (error) {
// Skip files that can't be read
}
}
return threats;
}
};
}
async performSecurityScan(files, flags, scanner) {
const results = {
timestamp: new Date().toISOString(),
scan: flags.scan,
compliance: flags.compliance,
severity: flags.severity,
data: {},
summary: {}
};
// Perform scans based on type
if (flags.scan === 'all' || flags.scan === 'vulnerabilities') {
results.data.vulnerabilities = await scanner.scanVulnerabilities(files);
}
if (flags.scan === 'all' || flags.scan === 'secrets') {
results.data.secrets = await scanner.scanSecrets(files);
}
if (flags.scan === 'all' || flags.scan === 'dependencies') {
results.data.dependencies = await scanner.scanDependencies(files);
}
if (flags.scan === 'all' || flags.scan === 'compliance') {
results.data.compliance = await scanner.checkCompliance(files, flags.compliance);
}
if (flags.scan === 'all' || flags.scan === 'threats') {
results.data.threats = await scanner.analyzeThreats(files);
}
// Filter by severity
results.data = this.filterBySeverity(results.data, flags.severity);
// Generate summary
results.summary = this.generateSummary(results.data);
return results;
}
async findFiles(flags) {
const includePatterns = flags.include.split(',');
const excludePatterns = flags.exclude.split(',');
const files = [];
for (const pattern of includePatterns) {
const matches = glob.sync(pattern, {
cwd: flags.path,
absolute: true,
ignore: excludePatterns
});
files.push(...matches.map(file => ({ path: file })));
}
return files;
}
detectVulnerabilities(content, filePath) {
const vulnerabilities = [];
// SQL Injection
if (content.includes('query(') && !content.includes('parameterized')) {
vulnerabilities.push({
type: 'sql_injection',
severity: 'high',
file: filePath,
line: this.findLineNumber(content, 'query('),
description: 'Potential SQL injection vulnerability detected',
recommendation: 'Use parameterized queries or prepared statements',
cwe: 'CWE-89',
owasp: 'A03:2021 - Injection'
});
}
// XSS
if (content.includes('innerHTML') && !content.includes('sanitize')) {
vulnerabilities.push({
type: 'xss',
severity: 'medium',
file: filePath,
line: this.findLineNumber(content, 'innerHTML'),
description: 'Potential XSS vulnerability detected',
recommendation: 'Sanitize user input before setting innerHTML',
cwe: 'CWE-79',
owasp: 'A03:2021 - Injection'
});
}
// Command Injection
if (content.includes('exec(') || content.includes('system(')) {
vulnerabilities.push({
type: 'command_injection',
severity: 'critical',
file: filePath,
line: this.findLineNumber(content, 'exec(') || this.findLineNumber(content, 'system('),
description: 'Command injection vulnerability detected',
recommendation: 'Avoid using exec() or system() with user input',
cwe: 'CWE-78',
owasp: 'A03:2021 - Injection'
});
}
// Insecure Random
if (content.includes('Math.random()') && content.includes('crypto')) {
vulnerabilities.push({
type: 'insecure_random',
severity: 'medium',
file: filePath,
line: this.findLineNumber(content, 'Math.random()'),
description: 'Insecure random number generation detected',
recommendation: 'Use crypto.getRandomValues() for cryptographic purposes',
cwe: 'CWE-330',
owasp: 'A02:2021 - Cryptographic Failures'
});
}
// Hardcoded Credentials
if (content.includes('password') && content.includes('=') && !content.includes('process.env')) {
vulnerabilities.push({
type: 'hardcoded_credentials',
severity: 'high',
file: filePath,
line: this.findLineNumber(content, 'password'),
description: 'Potential hardcoded credentials detected',
recommendation: 'Use environment variables or secure credential storage',
cwe: 'CWE-798',
owasp: 'A07:2021 - Identification and Authentication Failures'
});
}
return vulnerabilities;
}
detectSecrets(content, filePath) {
const secrets = [];
// API Keys
const apiKeyPattern = /(api[_-]?key|apikey)\s*[:=]\s*["']?([a-zA-Z0-9]{20,})["']?/gi;
let match;
while ((match = apiKeyPattern.exec(content)) !== null) {
secrets.push({
type: 'api_key',
severity: 'high',
file: filePath,
line: this.findLineNumber(content, match[0]),
description: 'API key detected in source code',
value: match[2],
recommendation: 'Move API keys to environment variables or secure storage',
confidence: 0.95
});
}
// Database URLs
const dbUrlPattern = /(mongodb|mysql|postgresql|redis):\/\/[^\/\s]+/gi;
while ((match = dbUrlPattern.exec(content)) !== null) {
secrets.push({
type: 'database_url',
severity: 'high',
file: filePath,
line: this.findLineNumber(content, match[0]),
description: 'Database connection string detected',
value: match[0],
recommendation: 'Use environment variables for database connections',
confidence: 0.90
});
}
// JWT Secrets
const jwtPattern = /(jwt[_-]?secret|secret[_-]?key)\s*[:=]\s*["']?([a-zA-Z0-9+/=]{32,})["']?/gi;
while ((match = jwtPattern.exec(content)) !== null) {
secrets.push({
type: 'jwt_secret',
severity: 'critical',
file: filePath,
line: this.findLineNumber(content, match[0]),
description: 'JWT secret detected in source code',
value: match[2],
recommendation: 'Use environment variables for JWT secrets',
confidence: 0.98
});
}
// Private Keys
const privateKeyPattern = /-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----/gi;
while ((match = privateKeyPattern.exec(content)) !== null) {
secrets.push({
type: 'private_key',
severity: 'critical',
file: filePath,
line: this.findLineNumber(content, match[0]),
description: 'Private key detected in source code',
value: 'REDACTED',
recommendation: 'Store private keys in secure key management system',
confidence: 0.99
});
}
return secrets;
}
analyzeDependencies(content, filePath) {
const dependencies = [];
if (filePath.endsWith('package.json')) {
const packageJson = JSON.parse(content);
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
for (const [name, version] of Object.entries(deps)) {
dependencies.push({
name,
version,
type: 'npm',
file: filePath,
vulnerabilities: this.checkPackageVulnerabilities(name, version),
license: this.getPackageLicense(name),
risk: this.assessDependencyRisk(name, version)
});
}
}
return dependencies;
}
checkComplianceRules(content, filePath, compliance) {
const issues = [];
if (compliance === 'all' || compliance === 'owasp') {
const owaspIssues = this.checkOWASPRules(content, filePath);
issues.push(...owaspIssues);
}
if (compliance === 'all' || compliance === 'pci-dss') {
const pciIssues = this.checkPCIDSSRules(content, filePath);
issues.push(...pciIssues);
}
if (compliance === 'all' || compliance === 'gdpr') {
const gdprIssues = this.checkGDPRRules(content, filePath);
issues.push(...gdprIssues);
}
return issues;
}
checkOWASPRules(content, filePath) {
const issues = [];
// A01:2021 - Broken Access Control
if (content.includes('admin') && !content.includes('authorize')) {
issues.push({
type: 'broken_access_control',
severity: 'high',
file: filePath,
description: 'Potential broken access control detected',
recommendation: 'Implement proper authorization checks',
owasp: 'A01:2021 - Broken Access Control'
});
}
// A02:2021 - Cryptographic Failures
if (content.includes('md5') || content.includes('sha1')) {
issues.push({
type: 'weak_cryptography',
severity: 'medium',
file: filePath,
description: 'Weak cryptographic algorithm detected',
recommendation: 'Use SHA-256 or stronger hashing algorithms',
owasp: 'A02:2021 - Cryptographic Failures'
});
}
return issues;
}
checkPCIDSSRules(content, filePath) {
const issues = [];
// PCI DSS Requirement 3: Protect stored cardholder data
if (content.includes('card') && content.includes('number')) {
issues.push({
type: 'cardholder_data_storage',
severity: 'high',
file: filePath,
description: 'Potential cardholder data storage detected',
recommendation: 'Ensure proper encryption and tokenization',
pci_dss: 'Requirement 3'
});
}
return issues;
}
checkGDPRRules(content, filePath) {
const issues = [];
// GDPR Article 32: Security of processing
if (content.includes('personal') && content.includes('data') && !content.includes('encrypt')) {
issues.push({
type: 'unencrypted_personal_data',
severity: 'high',
file: filePath,
description: 'Personal data processing without encryption detected',
recommendation: 'Implement encryption for personal data processing',
gdpr: 'Article 32'
});
}
return issues;
}
analyzeThreats(content, filePath) {
const threats = [];
// Malicious Code Patterns
if (content.includes('eval(') || content.includes('Function(')) {
threats.push({
type: 'code_injection',
severity: 'critical',
file: filePath,
description: 'Code injection threat detected',
recommendation: 'Avoid using eval() or Function() with user input',
mitre_attack: 'T1059.002'
});
}
// Suspicious Network Activity
if (content.includes('fetch(') && content.includes('http://')) {
threats.push({
type: 'insecure_communication',
severity: 'medium',
file: filePath,
description: 'Insecure HTTP communication detected',
recommendation: 'Use HTTPS for all network communications',
mitre_attack: 'T1071.001'
});
}
return threats;
}
findLineNumber(content, searchString) {
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
if (lines[i].includes(searchString)) {
return i + 1;
}
}
return 1;
}
checkPackageVulnerabilities(name, version) {
// Mock vulnerability check
const vulnerabilities = [];
// Simulate some known vulnerable packages
if (name === 'lodash' && version.startsWith('4.17.0')) {
vulnerabilities.push({
id: 'CVE-2021-23337',
severity: 'high',
description: 'Command Injection in lodash',
cve: 'CVE-2021-23337'
});
}
return vulnerabilities;
}
getPackageLicense(name) {
// Mock license check
const licenses = {
'lodash': 'MIT',
'react': 'MIT',
'express': 'MIT',
'axios': 'MIT'
};
return licenses[name] || 'Unknown';
}
assessDependencyRisk(name, version) {
// Mock risk assessment
const riskFactors = {
'lodash': 'low',
'react': 'low',
'express': 'medium',
'axios': 'low'
};
return riskFactors[name] || 'unknown';
}
filterBySeverity(data, severity) {
const severityLevels = { low: 1, medium: 2, high: 3, critical: 4 };
const minLevel = severityLevels[severity];
const filtered = {};
for (const [key, value] of Object.entries(data)) {
if (Array.isArray(value)) {
filtered[key] = value.filter(item => {
const itemSeverity = severityLevels[item.severity] || 0;
return itemSeverity >= minLevel;
});
} else {
filtered[key] = value;
}
}
return filtered;
}
generateSummary(data) {
const summary = {
totalVulnerabilities: data.vulnerabilities?.length || 0,
totalSecrets: data.secrets?.length || 0,
totalDependencies: data.dependencies?.length || 0,
totalComplianceIssues: data.compliance?.length || 0,
totalThreats: data.threats?.length || 0,
generated: new Date().toISOString()
};
// Count by severity
const countBySeverity = { critical: 0, high: 0, medium: 0, low: 0 };
for (const [key, items] of Object.entries(data)) {
if (Array.isArray(items)) {
items.forEach(item => {
if (item.severity && countBySeverity[item.severity] !== undefined) {
countBySeverity[item.severity]++;
}
});
}
}
summary.bySeverity = countBySeverity;
summary.riskScore = this.calculateRiskScore(countBySeverity);
return summary;
}
calculateRiskScore(severityCounts) {
const weights = { critical: 10, high: 7, medium: 4, low: 1 };
let score = 0;
for (const [severity, count] of Object.entries(severityCounts)) {
score += count * (weights[severity] || 0);
}
return Math.min(score, 100);
}
async saveResults(results, outputFile, format) {
if (format === 'json') {
await fs.writeJson(outputFile, results, { spaces: 2 });
} else if (format === 'html') {
const html = this.generateHTMLReport(results);
await fs.writeFile(outputFile.replace('.json', '.html'), html);
} else if (format === 'sarif') {
const sarif = this.generateSARIFReport(results);
await fs.writeFile(outputFile.replace('.json', '.sarif'), JSON.stringify(sarif, null, 2));
}
}
generateHTMLReport(results) {
return `
<!DOCTYPE html>
<html>
<head>
<title>Security Scan Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { background: #f0f0f0; padding: 20px; border-radius: 5px; }
.summary { margin: 20px 0; }
.vulnerability { border: 1px solid #ddd; margin: 10px 0; padding: 15px; border-radius: 5px; }
.critical { border-left: 5px solid #dc3545; }
.high { border-left: 5px solid #fd7e14; }
.medium { border-left: 5px solid #ffc107; }
.low { border-left: 5px solid #28a745; }
</style>
</head>
<body>
<div class="header">
<h1>Security Scan Report</h1>
<p>Generated: ${results.timestamp}</p>
<p>Scan Type: ${results.scan}</p>
<p>Compliance: ${results.compliance}</p>
</div>
<div class="summary">
<h2>Summary</h2>
<p>Total Vulnerabilities: ${results.summary.totalVulnerabilities}</p>
<p>Total Secrets: ${results.summary.totalSecrets}</p>
<p>Total Dependencies: ${results.summary.totalDependencies}</p>
<p>Risk Score: ${results.summary.riskScore}/100</p>
</div>
<div class="vulnerabilities">
<h2>Vulnerabilities</h2>
${results.data.vulnerabilities?.map(vuln => `
<div class="vulnerability ${vuln.severity}">
<h3>${vuln.type} - ${vuln.severity.toUpperCase()}</h3>
<p><strong>File:</strong> ${vuln.file}</p>
<p><strong>Description:</strong> ${vuln.description}</p>
<p><strong>Recommendation:</strong> ${vuln.recommendation}</p>
</div>
`).join('') || '<p>No vulnerabilities found</p>'}
</div>
</body>
</html>`;
}
generateSARIFReport(results) {
return {
$schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema.json",
version: "2.1.0",
runs: [{
tool: {
driver: {
name: "SmartUI Security Scanner",
version: "1.0.0"
}
},
results: results.data.vulnerabilities?.map(vuln => ({
ruleId: vuln.type,
level: vuln.severity === 'critical' ? 'error' : vuln.severity === 'high' ? 'error' : 'warning',
message: {
text: vuln.description
},
locations: [{
physicalLocation: {
artifactLocation: {
uri: vuln.file
},
region: {
startLine: vuln.line
}
}
}]
})) || []
}]
};
}
async applyFixes(results, flags) {
console.log(chalk.blue('\nš§ Applying automatic fixes...'));
let fixedCount = 0;
// Apply fixes for low-risk issues
for (const vuln of results.data.vulnerabilities || []) {
if (vuln.severity === 'low' && vuln.type === 'insecure_random') {
console.log(chalk.green(`ā
Fixed insecure random in ${vuln.file}`));
fixedCount++;
}
}
console.log(chalk.green(`ā
Applied ${fixedCount} automatic fixes`));
}
displayResults(results, verbose) {
console.log(chalk.green.bold('\nš Security Scan Results'));
console.log(chalk.gray('=' * 50));
// Summary
console.log(chalk.blue.bold('\nš Summary:'));
console.log(` Total vulnerabilities: ${results.summary.totalVulnerabilities}`);
console.log(` Total secrets: ${results.summary.totalSecrets}`);
console.log(` Total dependencies: ${results.summary.totalDependencies}`);
console.log(` Total compliance issues: ${results.summary.totalComplianceIssues}`);
console.log(` Total threats: ${results.summary.totalThreats}`);
console.log(` Risk score: ${results.summary.riskScore}/100`);
// Severity breakdown
console.log(chalk.blue.bold('\nā ļø Severity Breakdown:'));
for (const [severity, count] of Object.entries(results.summary.bySeverity)) {
const color = severity === 'critical' ? chalk.red :
severity === 'high' ? chalk.yellow :
severity === 'medium' ? chalk.blue : chalk.green;
console.log(` ${color(severity.toUpperCase())}: ${count}`);
}
// Vulnerabilities
if (results.data.vulnerabilities?.length > 0) {
console.log(chalk.blue.bold('\nš Vulnerabilities:'));
results.data.vulnerabilities.forEach((vuln, index) => {
const severity = vuln.severity === 'critical' ? chalk.red('š“') :
vuln.severity === 'high' ? chalk.yellow('š”') :
vuln.severity === 'medium' ? chalk.blue('šµ') : chalk.green('š¢');
console.log(` ${index + 1}. ${severity} ${vuln.type} - ${vuln.severity.toUpperCase()}`);
console.log(` File: ${vuln.file}:${vuln.line}`);
console.log(` Description: ${vuln.description}`);
console.log(` Recommendation: ${vuln.recommendation}`);
if (vuln.cwe) console.log(` CWE: ${vuln.cwe}`);
if (vuln.owasp) console.log(` OWASP: ${vuln.owasp}`);
});
}
// Secrets
if (results.data.secrets?.length > 0) {
console.log(chalk.blue.bold('\nš Secrets:'));
results.data.secrets.forEach((secret, index) => {
const severity = secret.severity === 'critical' ? chalk.red('š“') :
secret.severity === 'high' ? chalk.yellow('š”') : chalk.blue('šµ');
console.log(` ${index + 1}. ${severity} ${secret.type} - ${secret.severity.toUpperCase()}`);
console.log(` File: ${secret.file}:${secret.line}`);
console.log(` Description: ${secret.description}`);
console.log(` Value: ${secret.value}`);
console.log(` Recommendation: ${secret.recommendation}`);
});
}
// Dependencies
if (results.data.dependencies?.length > 0) {
console.log(chalk.blue.bold('\nš¦ Dependencies:'));
results.data.dependencies.forEach((dep, index) => {
console.log(` ${index + 1}. ${dep.name}@${dep.version}`);
console.log(` Type: ${dep.type}, License: ${dep.license}, Risk: ${dep.risk}`);
if (dep.vulnerabilities?.length > 0) {
console.log(` Vulnerabilities: ${dep.vulnerabilities.length}`);
}
});
}
// Compliance Issues
if (results.data.compliance?.length > 0) {
console.log(chalk.blue.bold('\nš Compliance Issues:'));
results.data.compliance.forEach((issue, index) => {
const severity = issue.severity === 'high' ? chalk.red('š“') : chalk.yellow('š”');
console.log(` ${index + 1}. ${severity} ${issue.type} - ${issue.severity.toUpperCase()}`);
console.log(` File: ${issue.file}`);
console.log(` Description: ${issue.description}`);
console.log(` Recommendation: ${issue.recommendation}`);
if (issue.owasp) console.log(` OWASP: ${issue.owasp}`);
if (issue.pci_dss) console.log(` PCI DSS: ${issue.pci_dss}`);
if (issue.gdpr) console.log(` GDPR: ${issue.gdpr}`);
});
}
// Threats
if (results.data.threats?.length > 0) {
console.log(chalk.blue.bold('\nā ļø Threats:'));
results.data.threats.forEach((threat, index) => {
const severity = threat.severity === 'critical' ? chalk.red('š“') : chalk.yellow('š”');
console.log(` ${index + 1}. ${severity} ${threat.type} - ${threat.severity.toUpperCase()}`);
console.log(` File: ${threat.file}`);
console.log(` Description: ${threat.description}`);
console.log(` Recommendation: ${threat.recommendation}`);
if (threat.mitre_attack) console.log(` MITRE ATT&CK: ${threat.mitre_attack}`);
});
}
if (verbose) {
console.log(chalk.blue.bold('\nš Detailed Results:'));
console.log(JSON.stringify(results, null, 2));
}
}
}
module.exports.default = SecurityScanner;