recoder-code
Version:
🚀 AI-powered development platform - Chat with 32+ models, build projects, automate workflows. Free models included!
492 lines • 20.9 kB
JavaScript
;
/**
* 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