Spaces:
Running
Running
| /** | |
| * Diff Analyzer | |
| * Maps n8n vulnerability data to the format expected by the VSCode extension. | |
| * Falls back to basic diff detection if n8n returns empty vulnerability arrays. | |
| */ | |
| /** | |
| * Severity mapping from n8n types to extension severity levels | |
| */ | |
| const SEVERITY_MAP = { | |
| 'SQL Injection': 'critical', | |
| 'Command Injection': 'critical', | |
| 'Insecure Deserialization': 'critical', | |
| 'Hardcoded Credentials': 'high', | |
| 'Path Traversal': 'high', | |
| 'XSS (Cross-Site Scripting)': 'high', | |
| 'Weak Cryptography': 'high', | |
| 'Debug Mode Enabled': 'medium', | |
| 'Insecure Random': 'medium', | |
| 'Missing Input Validation': 'medium', | |
| }; | |
| /** | |
| * Map n8n severity string to extension severity level | |
| */ | |
| function normalizeSeverity(severity, type) { | |
| if (!severity) return SEVERITY_MAP[type] || 'medium'; | |
| const s = severity.toLowerCase(); | |
| if (s === 'high' || s === 'critical') return SEVERITY_MAP[type] || 'high'; | |
| if (s === 'medium') return 'medium'; | |
| if (s === 'low') return 'low'; | |
| return 'medium'; | |
| } | |
| /** | |
| * Extract individual vulnerabilities from n8n response data. | |
| * Primary strategy: use n8n's per-vulnerability mappings directly. | |
| * Fallback: basic diff detection if n8n data is incomplete. | |
| * | |
| * @param {string} original - Original source code | |
| * @param {string} corrected - Corrected code from n8n | |
| * @param {string} filePath - File path for display | |
| * @param {Array} n8nVulnerabilities - Vulnerability array from n8n workflow | |
| * @param {Array} n8nVulnDetails - Vulnerability details from n8n (type + line) | |
| * @returns {Array} Vulnerability objects for the extension | |
| */ | |
| function extractVulnerabilities(original, corrected, filePath, n8nVulnerabilities = [], n8nVulnDetails = []) { | |
| // ββ Strategy 1: Direct n8n vulnerability mapping ββββββββββββββββββββββββββ | |
| if (n8nVulnerabilities && n8nVulnerabilities.length > 0) { | |
| console.log(`[DIFF] Mapping ${n8nVulnerabilities.length} vulnerabilities from n8n`); | |
| const vulns = n8nVulnerabilities.map(nv => { | |
| const originalCode = nv.original_code || nv.originalCode || ''; | |
| const fixedCode = nv.fixed_code || nv.fixedCode || ''; | |
| const lineNumber = nv.line_number || nv.line || 0; | |
| const type = nv.type || 'Security Issue'; | |
| const severity = normalizeSeverity(nv.severity, type); | |
| const issueText = nv.issue_text || nv.fix_recommendation || `${type} detected`; | |
| // Resolve line number from code if not provided | |
| let line = lineNumber; | |
| if (!line && originalCode) { | |
| // Find the line by matching the trimmed code content line-by-line | |
| const originalLines = original.split('\n'); | |
| const searchLines = originalCode.trim().split('\n'); | |
| // Try to find exact match first | |
| for (let i = 0; i <= originalLines.length - searchLines.length; i++) { | |
| let match = true; | |
| for (let j = 0; j < searchLines.length; j++) { | |
| if (originalLines[i + j].trim() !== searchLines[j].trim()) { | |
| match = false; | |
| break; | |
| } | |
| } | |
| if (match) { | |
| line = i + 1; // Convert to 1-indexed | |
| break; | |
| } | |
| } | |
| // If no exact match, try finding by the first significant line | |
| if (!line && searchLines.length > 0) { | |
| const firstSignificantLine = searchLines[0].trim(); | |
| if (firstSignificantLine) { | |
| for (let i = 0; i < originalLines.length; i++) { | |
| if (originalLines[i].trim() === firstSignificantLine) { | |
| line = i + 1; // Convert to 1-indexed | |
| console.warn(`[DIFF] Used fuzzy match for line number: ${line}`); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Validate line number | |
| if (!line || line < 1) { | |
| console.error(`[DIFF] Invalid line number: ${line} for type: ${type}`); | |
| line = 1; // Fallback to line 1 | |
| } | |
| const endLine = line + (originalCode ? originalCode.split('\n').length - 1 : 0); | |
| return { | |
| file: filePath, | |
| line, | |
| endLine, | |
| type, | |
| severity, | |
| description: `${type} at line ${line}: ${issueText}`, | |
| originalCode, | |
| fixedCode, | |
| status: 'analyzed', | |
| isFixed: false, | |
| result: fixedCode | |
| }; | |
| }); | |
| // Sort by line number | |
| vulns.sort((a, b) => a.line - b.line); | |
| return vulns; | |
| } | |
| // ββ Strategy 2: Fallback β code changed but no vulnerability data βββββββββ | |
| if (original !== corrected) { | |
| console.log('[DIFF] No n8n vulnerability data, creating generic vulnerability from diff'); | |
| return [{ | |
| file: filePath, | |
| line: 1, | |
| endLine: original.split('\n').length, | |
| type: 'Security Issues', | |
| severity: 'medium', | |
| description: 'Multiple security improvements applied', | |
| originalCode: original, | |
| fixedCode: corrected, | |
| status: 'analyzed', | |
| isFixed: false, | |
| result: corrected | |
| }]; | |
| } | |
| // No changes detected | |
| return []; | |
| } | |
| /** | |
| * Apply a specific vulnerability fix to the full file code. | |
| * Used by the /api/apply-individual-fix endpoint. | |
| * | |
| * @param {string} fullCode - Full file code | |
| * @param {Object} vulnerability - Vulnerability object with line, endLine, fixedCode | |
| * @returns {string} Code with the fix applied | |
| */ | |
| function applyIndividualFix(fullCode, vulnerability) { | |
| const lines = fullCode.split('\n'); | |
| const fixedLines = vulnerability.fixedCode.split('\n'); | |
| const startIdx = (vulnerability.line || 1) - 1; | |
| const endIdx = (vulnerability.endLine || vulnerability.line || 1) - 1; | |
| const deleteCount = endIdx - startIdx + 1; | |
| lines.splice(startIdx, deleteCount, ...fixedLines); | |
| return lines.join('\n'); | |
| } | |
| module.exports = { | |
| extractVulnerabilities, | |
| applyIndividualFix, | |
| normalizeSeverity, | |
| SEVERITY_MAP | |
| }; | |