UNPKG

smartui-migration-tool

Version:

Enterprise-grade CLI tool for migrating visual testing platforms to LambdaTest SmartUI

858 lines (743 loc) • 28.2 kB
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;