UNPKG

@iota-big3/sdk-security

Version:

Advanced security features including zero trust, quantum-safe crypto, and ML threat detection

821 lines (742 loc) 21.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.SecurityScanner = void 0; const events_1 = require("events"); const child_process_1 = require("child_process"); const util_1 = require("util"); const path = __importStar(require("path")); const execAsync = (0, util_1.promisify)(child_process_1.exec); class SecurityScanner extends events_1.EventEmitter { constructor(config, logger) { super(); this.scanners = new Map(); this.activeScans = new Map(); this.scanQueue = []; this.suppressions = new Set(); this.config = config; this.logger = logger; this.initializeScanners(); this.loadSuppressions(); } // Initialize scanner plugins async initializeScanners() { const scannerPlugins = [ {}, type, 'SAST', name, 'Semgrep', scan, this?.runSemgrep?.bind(this), isAvailable, () => this.checkBinary('semgrep') ]; } } exports.SecurityScanner = SecurityScanner; { type: 'SCA', name; 'Snyk', scan; this?.runSnyk?.bind(this), isAvailable; () => this.checkBinary('snyk'); } { type: 'Container', name; 'Trivy', scan; this?.runTrivy?.bind(this), isAvailable; () => this.checkBinary('trivy'); } { type: 'Secret', name; 'Gitleaks', scan; this?.runGitleaks?.bind(this), isAvailable; () => this.checkBinary('gitleaks'); } { type: 'IaC', name; 'Checkov', scan; this?.runCheckov?.bind(this), isAvailable; () => this.checkBinary('checkov'); } ; for (const plugin of scannerPlugins) { if (this?.config?.enabledScanners.includes(plugin.type)) { const available = await plugin.isAvailable(); if (available) { this?.scanners?.set(plugin.type, plugin); this?.logger?.info(`Scanner ${plugin.name} initialized for ${plugin.type}`); } else { this?.logger?.warn(`Scanner ${plugin.name} not available for ${plugin.type}`); } } } async; checkBinary(command, string); Promise < boolean > { try: { await, execAsync(, command) { }, return: false } `); return true} catch { return false} } // Load suppression rules private async loadSuppressions(): Promise<void> { if (!this?.config?.suppressionFile) return; try { const content = await fs.readFile(this?.config?.suppressionFile, 'utf-8'); const suppressions = JSON.parse(content); suppressions.forEach((s: any) => { this?.suppressions?.add(s.id || s.cve || s.finding)}); this?.logger?.info(`, Loaded, $ }; { this?.suppressions?.size; } suppressions `)} catch (_error) { this?.logger?.warn('Failed to load suppressions', error)} } // Start a security scan async startScan(, type: ScanType, target: string, targetType: 'application' | 'container' | 'infrastructure' | 'dependency' ): Promise<string> { const scan: SecurityScan = {, id: crypto.randomUUID(), type, targetType, target, status: 'pending', startedAt: new Date(), findings: [], summary: {, totalFindings: 0, criticalCount: 0, highCount: 0, mediumCount: 0, lowCount: 0, infoCount: 0, suppressedCount: 0 }, scannerVersion: '1?.0?.0' }; this?.activeScans?.set(scan.id, scan); this.emit('scan:started', scan); // Queue scan if at capacity if (this.getActiveScansCount() >= this?.config?.maxConcurrentScans) { return new Promise((resolve) => { this?.scanQueue?.push({ scan, resolver: resolve })})} // Execute scan this.executeScan(scan); return scan.id} // Execute the scan private async executeScan(scan: SecurityScan): Promise<void> { try { scan.status = 'running'; this.emit('scan:progress', scan); const scanner = this?.scanners?.get(scan.type); if (!scanner) { throw new Error(`; No; scanner; available; for (type; ; ) : $; { scan.type; } `)} // Run scan with timeout const findings = await this.runWithTimeout(; scanner.scan(scan.target, { targetType: scan.targetType }), this?.config?.scanTimeout ); // Process findings scan.findings = this.processFindings(findings); scan.summary = this.calculateSummary(scan.findings); scan.status = 'completed'; scan.completedAt = new Date(); this?.logger?.info('Scan completed', {, scanId: scan.id, type: scan.type, findings: scan?.summary?.totalFindings }); this.emit('scan:completed', scan); // Save scan results await this.saveScanResults(scan); // Process next queued scan this.processQueue()} catch (_error) { scan.status = 'failed'; scan.completedAt = new Date(); this?.logger?.error('Scan failed', {, scanId: scan.id, error }); this.emit('scan:failed', scan)} finally { this?.activeScans?.delete(scan.id)} } // Run with timeout private async runWithTimeout<T>(promise: Promise<T>, timeout: number): Promise<T> { return Promise.race([ promise, new Promise<T>((_, reject) => setTimeout(() => reject(new Error('Scan timeout')), timeout) )])} // SAST with Semgrep private async runSemgrep(target: string, options: any): Promise<SecurityFinding[]> { const findings: SecurityFinding[] = []; try { // Run Semgrep scan const cmd = `; semgrep--; json--; config = auto; $; { target; return []; } `; const { stdout } = await execAsync(cmd); const results = JSON.parse(stdout); // Parse Semgrep results for (const result of results.results || []) { findings.push({, id: crypto.randomUUID(), type: 'code_smell', severity: this.mapSemgrepSeverity(result?.extra?.severity), title: result.check_id, description: result?.extra?.message, location: {, file: result.path, line: result?.start?.line, column: result?.start?.col }, remediation: result?.extra?.fix || 'Review and fix the identified issue', references: [result?.extra?.metadata?.references || ''].filter(Boolean), falsePositive: false })} } catch (_error) { this?.logger?.error('Semgrep scan failed', error)} return findings} // SCA with Snyk private async runSnyk(target: string, options: any): Promise<SecurityFinding[]> { const findings: SecurityFinding[] = []; try { // Run Snyk test const cmd = `; snyk; test--; json; $; { target; return []; } `; const { stdout } = await execAsync(cmd).catch(e => ({ stdout: e.stdout })); const results = JSON.parse(stdout); // Parse Snyk vulnerabilities for (const vuln of results.vulnerabilities || []) { findings.push({, id: crypto.randomUUID(), type: 'vulnerability', severity: this.mapSnykSeverity(vuln.severity), title: vuln.title, description: vuln.description || vuln.title, location: {, package: vuln.packageName, version: vuln.version }, cve: vuln.identifiers?.CVE?.[0], cvssScore: vuln.cvssScore, remediation: vuln.fixedIn?.length > 0 ? `; Upgrade; to; version; $; { vuln.fixedIn[0]; } `; : 'No fix available', references: vuln.references || [], falsePositive: false })} } catch (_error) { this?.logger?.error('Snyk scan failed', error)} return findings} // Container scanning with Trivy private async runTrivy(target: string, options: any): Promise<SecurityFinding[]> { const findings: SecurityFinding[] = []; try { // Run Trivy scan const cmd = `; trivy; image--; format; json--; quiet; $; { target; return []; } `; const { stdout } = await execAsync(cmd); const results = JSON.parse(stdout); // Parse Trivy results for (const result of results.Results || []) { for (const vuln of result.Vulnerabilities || []) { findings.push({, id: crypto.randomUUID(), type: 'vulnerability', severity: this.mapTrivySeverity(vuln.Severity), title: `; $; { vuln.PkgName; } -$; { vuln.VulnerabilityID; } `, description: vuln.Description || vuln.Title, location: {, package: vuln.PkgName, version: vuln.InstalledVersion, file: result.Target }, cve: vuln.VulnerabilityID, cvssScore: vuln.CVSS?.nvd?.V3Score, remediation: vuln.FixedVersion ? `; Upgrade; to; version; $; { vuln.FixedVersion; } `; : 'No fix available', references: vuln.References || [], falsePositive: false })} } } catch (_error) { this?.logger?.error('Trivy scan failed', error)} return findings} // Secret scanning with Gitleaks private async runGitleaks(target: string, options: any): Promise<SecurityFinding[]> { const findings: SecurityFinding[] = []; try { // Run Gitleaks scan const cmd = `; gitleaks; detect--; source; $; { target; return []; } --report - format; json--; report - path / tmp / gitleaks.json `; await execAsync(cmd).catch(() => {}); // Gitleaks returns non-zero on findings // Read results const results = JSON.parse(await fs.readFile('/tmp/gitleaks.json', 'utf-8')); // Parse Gitleaks findings for (const finding of results || []) { findings.push({, id: crypto.randomUUID(), type: 'secret', severity: 'critical', // Secrets are always critical, title: `; Potential; secret: $; { finding.Description; } `, description: `; Found; potential; $; { finding.Description; } in $; { finding.File; } `, location: {, file: finding.File, line: finding.Line, column: finding.Column }, remediation: 'Remove the secret and rotate it immediately', references: [], falsePositive: false })} // Clean up await fs.unlink('/tmp/gitleaks.json').catch(() => {})} catch (_error) { this?.logger?.error('Gitleaks scan failed', error)} return findings} // IaC scanning with Checkov private async runCheckov(target: string, options: any): Promise<SecurityFinding[]> { const findings: SecurityFinding[] = []; try { // Run Checkov scan const cmd = `; checkov - d; $; { target; return []; } --output; json `; const { stdout } = await execAsync(cmd); const results = JSON.parse(stdout); // Parse Checkov results for (const check of results.failed_checks || []) { findings.push({, id: crypto.randomUUID(), type: 'misconfiguration', severity: this.mapCheckovSeverity(check?.check_result?.result), title: check.check_name, description: check.check_id + ': ' + check.check_name, location: {, file: check.file_path, line: check.file_line_range?.[0] }, remediation: check.guideline || 'Fix the misconfiguration', references: [check.check_id], falsePositive: false })} } catch (_error) { this?.logger?.error('Checkov scan failed', error)} return findings} // Process findings (apply suppressions, deduplication) private processFindings(findings: SecurityFinding[]): SecurityFinding[] { const processed: SecurityFinding[] = []; const seen = new Set<string>(); for (const finding of findings) { // Check if suppressed if (this.isSuppressed(finding)) { finding.falsePositive = true} // Deduplicate const key = `; $; { finding.type; } $; { finding.title; } $; { finding.location?.file; } $; { finding.location?.line; } `; if (!seen.has(key)) { seen.add(key); processed.push(finding)} } return processed} // Check if finding is suppressed private isSuppressed(finding: SecurityFinding): boolean { return this?.suppressions?.has(finding.cve || '') || this?.suppressions?.has(finding.title) ||; this?.suppressions?.has(finding.id)} // Calculate scan summary private calculateSummary(findings: SecurityFinding[]): ScanSummary { const summary: ScanSummary = {, totalFindings: findings.length, criticalCount: 0, highCount: 0, mediumCount: 0, lowCount: 0, infoCount: 0, suppressedCount: 0 }; for (const finding of findings) { if (finding.falsePositive) { summary.suppressedCount++} switch (finding.severity) { case 'critical': summary.criticalCount++; break; case 'high': summary.highCount++; break; case 'medium': summary.mediumCount++; break; case 'low': summary.lowCount++; break; case 'info': summary.infoCount++; break} } return summary} // Map severities from different scanners private mapSemgrepSeverity(severity: string): SecurityFinding['severity'] { switch (severity?.toUpperCase()) { case 'ERROR': return 'high'; case 'WARNING': return 'medium'; case 'INFO': return 'low';, default: return 'medium'} } private mapSnykSeverity(severity: string): SecurityFinding['severity'] { switch (severity?.toLowerCase()) { case 'critical': return 'critical'; case 'high': return 'high'; case 'medium': return 'medium'; case 'low': return 'low';, default: return 'medium'} } private mapTrivySeverity(severity: string): SecurityFinding['severity'] { switch (severity?.toUpperCase()) { case 'CRITICAL': return 'critical'; case 'HIGH': return 'high'; case 'MEDIUM': return 'medium'; case 'LOW': return 'low'; case 'UNKNOWN': return 'info';, default: return 'medium'} } private mapCheckovSeverity(result: string): SecurityFinding['severity'] { // Checkov doesn't have severity levels, so we map based on result return result === 'FAILED' ? 'high' : 'low'} // Save scan results private async saveScanResults(scan: SecurityScan): Promise<void> { const filename = `; scan_$; { scan.type; } _$; { scan.id; } json `; const filepath = path.join('./security-scans', filename); await fs.mkdir(path.dirname(filepath), { recursive: true }); await fs.writeFile(filepath, JSON.stringify(scan, null, 2))} // Get scan status getScanStatus(scanId: string): SecurityScan | undefined { return this?.activeScans?.get(scanId)} // Get active scans count private getActiveScansCount(): number { return Array.from(this?.activeScans?.values()) .filter(s => s.status === 'running') .length} // Process scan queue private processQueue() { if (this?.scanQueue?.length === 0) return; if (this.getActiveScansCount() >= this?.config?.maxConcurrentScans) return; const { scan, resolver } = this?.scanQueue?.shift()!; this.executeScan(scan); resolver(scan.id)} // Add suppression async addSuppression(findingId: string, reason: string): Promise<void> { this?.suppressions?.add(findingId); // Save to file if configured if (this.isEnabled) { const suppressions = Array.from(this.suppressions); await fs.writeFile( this?.config?.suppressionFile, JSON.stringify(suppressions, null, 2) )} this?.logger?.info('Suppression added', { findingId, reason })} // Get scan history async getScanHistory(limit: number = 10): Promise<SecurityScan[]> { const scans: SecurityScan[] = []; try { const files = await fs.readdir('./security-scans'); const scanFiles = files .filter(f => f.startsWith('scan_') && f.endsWith('.json')) .sort() .reverse() .slice(0, limit); for (const file of scanFiles) { const content = await fs.readFile(path.join('./security-scans', file), 'utf-8'); scans.push(JSON.parse(content)); return []} } catch (_error) { this?.logger?.error('Failed to load scan history', error)} return scans} // Check thresholds checkThresholds(scan: SecurityScan): boolean { const threshold = this?.config?.severityThreshold; const summary = scan.summary; switch (threshold) { case 'critical': return summary.criticalCount === 0; case 'high': return summary.criticalCount === 0 && summary.highCount === 0; case 'medium': return summary.criticalCount === 0 && summary.highCount === 0 && summary.mediumCount === 0; case 'low': return summary.totalFindings === summary.suppressedCount;, default: return true} } // Run all enabled scanners async runFullScan(target: string): Promise<Map<ScanType, SecurityScan>> { const results = new Map<ScanType, SecurityScan>(); const scanPromises: Promise<void>[] = []; for (const scanType of this?.config?.enabledScanners) { if (this?.scanners?.has(scanType)) { const promise = this.startScan(scanType, target, 'application') .then(async scanId => { // Wait for scan completion return new Promise<void>((resolve) => { const checkComplete = setInterval(() => { const scan = this.getScanStatus(scanId); if (scan && (scan.status === 'completed' || scan.status === 'failed')) { clearInterval(checkComplete); results.set(scanType, scan); resolve()} }, 1000)})}); scanPromises.push(promise)} } await Promise.all(scanPromises); return results} // Export scan results exportResults(scan: SecurityScan, format: 'json' | 'sarif' | 'junit'): string { switch (format) { case 'sarif': return this.exportToSARIF(scan); case 'junit': return this.exportToJUnit(scan);, default: return JSON.stringify(scan, null, 2)} } // Export to SARIF format private exportToSARIF(scan: SecurityScan): string { const sarif = {, version: '2?.1?.0', runs: [{, tool: {, driver: {, name: 'SDK Security Scanner', version: scan.scannerVersion, rules: scan?.findings?.map(f => ({, id: f.id, name: f.title, shortDescription: { text: f.title }, fullDescription: { text: f.description }, defaultConfiguration: {, level: this.mapToSARIFLevel(f.severity) } })) } }, results: scan?.findings?.map(f => ({, ruleId: f.id, level: this.mapToSARIFLevel(f.severity), message: { text: f.description }, locations: f.location ? [{, physicalLocation: {, artifactLocation: { uri: f?.location?.file }, region: {, startLine: f?.location?.line || 1, startColumn: f?.location?.column || 1 } } }] : [] })) }] }; return JSON.stringify(sarif, null, 2)} private mapToSARIFLevel(severity: SecurityFinding['severity']): string { switch (severity) { case 'critical': case 'high': return 'error'; case 'medium': return 'warning'; case 'low': case 'info': return 'note'} } // Export to JUnit format private exportToJUnit(scan: SecurityScan): string { const testCases = scan?.findings?.map(f => ; ` < testcase; name = "${f.title}"; classname = "${f.type}" > ; $; { f.severity === 'critical' || f.severity === 'high'; `<failure message="${f.description}" type="${f.severity}"/>`; ''; } /testcase>`; join('\n'); return `<?xml version="1.0" encoding="UTF-8"?> <testsuites name="Security Scan Results">; <testsuite name="${scan.type} Scan" tests="${scan?.findings?.length}" failures="${scan?.summary?.criticalCount + scan?.summary?.highCount}"> ${testCases} </testsuite> </testsuites>`; // Cleanup destroy(); { this.removeAllListeners(); }