UNPKG

recoder-code

Version:

🚀 AI-powered development platform - Chat with 32+ models, build projects, automate workflows. Free models included!

492 lines • 20.9 kB
"use strict"; /** * SecurityService * Handles security scanning, vulnerability detection, and threat analysis */ 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 (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SecurityService = void 0; const crypto = __importStar(require("crypto")); const tar = __importStar(require("tar")); const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); class SecurityService { constructor(config) { this.config = config; this.scannerVersion = '1.0.0'; this.logger = { log: (message) => console.log(`[SecurityService] ${message}`), warn: (message, error) => console.warn(`[SecurityService] ${message}`, error), error: (message, error) => console.error(`[SecurityService] ${message}`, error) }; } async scanTarball(tarballBuffer) { // Simple security scan - just return passed for now return { passed: true, issues: [] }; } async scanPackage(packageBuffer, packageVersion, options = {}) { const startTime = Date.now(); this.logger.log(`Starting security scan for ${packageVersion.package?.name}@${packageVersion.version}`); try { const { deep_scan = true, check_dependencies = true, malware_detection = true, license_check = true, timeout = 300000 // 5 minutes } = options; const results = { status: 'clean', vulnerabilities: [], threats: [], malware_detected: false, risk_score: 0, scan_duration: 0, scanner_version: this.scannerVersion }; // Create temporary directory for scanning const tempDir = path.join('/tmp', `scan_${crypto.randomUUID()}`); await fs.ensureDir(tempDir); try { // Extract package await this.extractPackage(packageBuffer, tempDir); // Run parallel scans with timeout const scanPromises = [ this.scanForVulnerabilities(tempDir, packageVersion), malware_detection ? this.scanForMalware(tempDir) : Promise.resolve([]), this.scanForSuspiciousPatterns(tempDir), check_dependencies ? this.scanDependencies(tempDir, packageVersion) : Promise.resolve([]), license_check ? this.scanLicenses(tempDir) : Promise.resolve([]) ]; // Helper to add timeout to Promise.all async function withTimeout(promise, ms) { return new Promise((resolve, reject) => { const timer = setTimeout(() => reject(new Error('Scan timeout')), ms); promise.then((val) => { clearTimeout(timer); resolve(val); }, (err) => { clearTimeout(timer); reject(err); }); }); } const [vulnerabilities, malwareThreats, suspiciousThreats, dependencyVulns, licenseIssues] = await withTimeout(Promise.all(scanPromises), timeout); // Combine results results.vulnerabilities = [...vulnerabilities, ...dependencyVulns]; results.threats = [...malwareThreats, ...suspiciousThreats, ...licenseIssues]; results.malware_detected = malwareThreats.some(t => t.type === 'malware'); // Calculate risk score results.risk_score = this.calculateRiskScore(results); // Determine overall status results.status = this.determineStatus(results); results.scan_duration = Date.now() - startTime; this.logger.log(`Security scan completed for ${packageVersion.package?.name}@${packageVersion.version}: ` + `${results.status} (${results.vulnerabilities.length} vulns, ${results.threats.length} threats)`); return results; } finally { // Cleanup await fs.remove(tempDir).catch(err => this.logger.warn(`Failed to cleanup temp dir: ${err.message}`)); } } catch (error) { const err = error; this.logger.error(`Security scan failed: ${err.message}`, err.stack); return { status: 'critical', vulnerabilities: [], threats: [{ type: 'suspicious_code', severity: 'critical', description: `Scan failed: ${error instanceof Error ? error.message : String(error)}`, evidence: [], confidence: 0.5 }], malware_detected: false, risk_score: 100, scan_duration: Date.now() - startTime, scanner_version: this.scannerVersion }; } } async extractPackage(packageBuffer, extractPath) { const tarStream = tar.extract({ cwd: extractPath, strip: 1, filter: (path) => { // Security: Prevent path traversal const normalizedPath = path.normalize(path); return !normalizedPath.includes('..') && normalizedPath.length < 255; } }); return new Promise((resolve, reject) => { tarStream.on('error', reject); tarStream.on('end', resolve); tarStream.write(packageBuffer); tarStream.end(); }); } async scanForVulnerabilities(extractPath, packageVersion) { const vulnerabilities = []; try { // Check known vulnerability databases const packageJson = await this.readPackageJson(extractPath); if (!packageJson) return vulnerabilities; // Simulate vulnerability checking against known databases // In production, this would integrate with npm audit, Snyk, or similar services // Check for outdated dependencies if (packageVersion.dependencies) { for (const [depName, depVersion] of Object.entries(packageVersion.dependencies)) { const vulns = await this.checkDependencyVulnerabilities(depName, depVersion); vulnerabilities.push(...vulns); } } // Check for known vulnerable patterns in code const codeVulns = await this.scanCodeForVulnerabilities(extractPath); vulnerabilities.push(...codeVulns); } catch (error) { this.logger.warn(`Vulnerability scan failed: ${error.message}`); } return vulnerabilities; } async scanForMalware(extractPath) { const threats = []; try { // Scan for malicious patterns const files = await this.getAllFiles(extractPath); for (const file of files) { const content = await fs.readFile(file, 'utf8').catch(() => null); if (!content) continue; // Check for suspicious patterns const malwarePatterns = [ /eval\s*\(\s*(?:atob|Buffer\.from)/gi, /child_process\s*\.\s*exec\s*\(\s*['"`][\w\s\/\-\\]*rm\s+\-rf/gi, /crypto\.createCipher.*password/gi, /require\s*\(\s*['"`]http[s]?:\/\//gi, /\.download\s*\(\s*['"`]http/gi, /process\.env\[['"`]([A-Z_]+)['"`]\]/gi // Environment variable access ]; for (const pattern of malwarePatterns) { const matches = content.match(pattern); if (matches) { threats.push({ type: 'malware', severity: 'high', description: `Suspicious pattern detected in ${path.relative(extractPath, file)}`, evidence: matches.slice(0, 3), confidence: 0.7 }); } } } } catch (error) { this.logger.warn(`Malware scan failed: ${error.message}`); } return threats; } async scanForSuspiciousPatterns(extractPath) { const threats = []; try { const packageJson = await this.readPackageJson(extractPath); if (!packageJson) return threats; // Check for typosquatting if (this.isLikelyTyposquat(packageJson.name)) { threats.push({ type: 'typosquatting', severity: 'medium', description: 'Package name resembles popular package (potential typosquatting)', evidence: [packageJson.name], confidence: 0.6 }); } // Check for suspicious scripts if (packageJson.scripts) { for (const [scriptName, script] of Object.entries(packageJson.scripts)) { if (this.isSuspiciousScript(script)) { threats.push({ type: 'suspicious_code', severity: 'medium', description: `Suspicious script detected: ${scriptName}`, evidence: [script], confidence: 0.5 }); } } } // Check for data exfiltration patterns const files = await this.getAllFiles(extractPath); for (const file of files) { const content = await fs.readFile(file, 'utf8').catch(() => null); if (!content) continue; if (this.hasDataExfiltrationPatterns(content)) { threats.push({ type: 'data_exfiltration', severity: 'high', description: `Potential data exfiltration code in ${path.relative(extractPath, file)}`, evidence: ['Sensitive data collection patterns detected'], confidence: 0.6 }); } } } catch (error) { this.logger.warn(`Suspicious pattern scan failed: ${error.message}`); } return threats; } async scanDependencies(extractPath, packageVersion) { const vulnerabilities = []; try { // This would integrate with npm audit or similar tools // For now, we'll do basic checks const allDeps = { ...packageVersion.dependencies, ...packageVersion.dev_dependencies, ...packageVersion.peer_dependencies, ...packageVersion.optional_dependencies }; for (const [depName, depVersion] of Object.entries(allDeps)) { const vulns = await this.checkDependencyVulnerabilities(depName, depVersion); vulnerabilities.push(...vulns); } } catch (error) { this.logger.warn(`Dependency scan failed: ${error.message}`); } return vulnerabilities; } async scanLicenses(extractPath) { const threats = []; try { const packageJson = await this.readPackageJson(extractPath); if (!packageJson) return threats; // Check for license issues if (!packageJson.license || packageJson.license === 'UNLICENSED') { threats.push({ type: 'suspicious_code', severity: 'low', description: 'Package has no license or is unlicensed', evidence: [packageJson.license || 'No license specified'], confidence: 0.3 }); } // Check for restrictive licenses const restrictiveLicenses = ['GPL-3.0', 'AGPL-3.0', 'SSPL-1.0']; if (restrictiveLicenses.includes(packageJson.license)) { threats.push({ type: 'suspicious_code', severity: 'low', description: 'Package uses restrictive license', evidence: [packageJson.license], confidence: 0.2 }); } } catch (error) { this.logger.warn(`License scan failed: ${error.message}`); } return threats; } async checkDependencyVulnerabilities(depName, depVersion) { // This would integrate with vulnerability databases // For now, return empty array return []; } async scanCodeForVulnerabilities(extractPath) { const vulnerabilities = []; try { const files = await this.getAllFiles(extractPath, ['.js', '.ts', '.json']); for (const file of files) { const content = await fs.readFile(file, 'utf8').catch(() => null); if (!content) continue; // Check for known vulnerable patterns if (content.includes('eval(') || content.includes('Function(')) { vulnerabilities.push({ id: 'code-injection', severity: 'high', title: 'Code Injection Risk', description: 'Use of eval() or Function() constructor detected', affected_versions: ['*'], patched_versions: [], recommendation: 'Remove use of eval() and Function() constructor', references: ['https://owasp.org/www-community/attacks/Code_Injection'] }); } if (content.includes('innerHTML') && !content.includes('sanitize')) { vulnerabilities.push({ id: 'xss-risk', severity: 'medium', title: 'XSS Risk', description: 'Use of innerHTML without sanitization detected', affected_versions: ['*'], patched_versions: [], recommendation: 'Use textContent or sanitize HTML content', references: ['https://owasp.org/www-community/attacks/xss/'] }); } } } catch (error) { this.logger.warn(`Code vulnerability scan failed: ${error.message}`); } return vulnerabilities; } async readPackageJson(extractPath) { try { const packageJsonPath = path.join(extractPath, 'package.json'); const content = await fs.readFile(packageJsonPath, 'utf8'); return JSON.parse(content); } catch { return null; } } async getAllFiles(dir, extensions) { const files = []; const items = await fs.readdir(dir); for (const item of items) { const fullPath = path.join(dir, item); const stat = await fs.stat(fullPath); if (stat.isDirectory() && !item.startsWith('.') && item !== 'node_modules') { files.push(...await this.getAllFiles(fullPath, extensions)); } else if (stat.isFile()) { if (!extensions || extensions.some(ext => item.endsWith(ext))) { files.push(fullPath); } } } return files; } isLikelyTyposquat(packageName) { const popularPackages = [ 'react', 'vue', 'angular', 'lodash', 'express', 'axios', 'moment', 'jquery', 'bootstrap', 'webpack', 'babel', 'typescript', 'eslint' ]; return popularPackages.some(popular => { const distance = this.levenshteinDistance(packageName.toLowerCase(), popular); return distance > 0 && distance <= 2 && packageName.length >= popular.length - 1; }); } isSuspiciousScript(script) { const suspiciousPatterns = [ /curl\s+.*\|\s*sh/, /wget\s+.*\|\s*sh/, /rm\s+-rf/, /chmod\s+\+x/, /base64\s+-d/, /eval\s*\$/ ]; return suspiciousPatterns.some(pattern => pattern.test(script)); } hasDataExfiltrationPatterns(content) { const patterns = [ /process\.env/g, /require\s*\(\s*['"`]os['"`]\s*\)/g, /\.hostname\(\)/g, /\.userInfo\(\)/g, /\.networkInterfaces\(\)/g ]; let suspiciousCount = 0; for (const pattern of patterns) { if (pattern.test(content)) { suspiciousCount++; } } return suspiciousCount >= 3; // Multiple indicators } levenshteinDistance(str1, str2) { const matrix = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null)); for (let i = 0; i <= str1.length; i++) matrix[0][i] = i; for (let j = 0; j <= str2.length; j++) matrix[j][0] = j; for (let j = 1; j <= str2.length; j++) { for (let i = 1; i <= str1.length; i++) { const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1; matrix[j][i] = Math.min(matrix[j][i - 1] + 1, matrix[j - 1][i] + 1, matrix[j - 1][i - 1] + indicator); } } return matrix[str2.length][str1.length]; } calculateRiskScore(results) { let score = 0; // Vulnerability scoring for (const vuln of results.vulnerabilities) { switch (vuln.severity) { case 'critical': score += 25; break; case 'high': score += 15; break; case 'medium': score += 8; break; case 'low': score += 3; break; } } // Threat scoring for (const threat of results.threats) { const baseScore = { 'critical': 20, 'high': 12, 'medium': 6, 'low': 2 }[threat.severity]; score += baseScore * threat.confidence; } // Malware detection if (results.malware_detected) { score += 50; } return Math.min(100, Math.round(score)); } determineStatus(results) { if (results.malware_detected) return 'critical'; if (results.risk_score >= 70) return 'critical'; if (results.risk_score >= 30) return 'warning'; const hasCriticalVuln = results.vulnerabilities.some(v => v.severity === 'critical'); const hasCriticalThreat = results.threats.some(t => t.severity === 'critical'); if (hasCriticalVuln || hasCriticalThreat) return 'critical'; const hasHighRisk = results.vulnerabilities.some(v => v.severity === 'high') || results.threats.some(t => t.severity === 'high'); if (hasHighRisk) return 'warning'; return 'clean'; } } exports.SecurityService = SecurityService; //# sourceMappingURL=SecurityService.js.map