UNPKG

ubon

Version:

Security scanner for AI-generated apps (Cursor, Lovable, Windsurf, v0). Catches hardcoded secrets, prompt injection, hallucinated imports, Server Actions / Edge runtime mistakes, and the vibe-coded vulnerabilities traditional linters miss.

147 lines • 5.47 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.collectFixEdits = collectFixEdits; exports.applyFixes = applyFixes; exports.previewFixes = previewFixes; exports.printFixPreviews = printFixPreviews; const fs_1 = require("fs"); const path_1 = require("path"); function collectFixEdits(results) { const map = new Map(); for (const r of results) { if (!r.fixEdits || r.fixEdits.length === 0) continue; for (const e of r.fixEdits) { if (!map.has(e.file)) map.set(e.file, []); map.get(e.file).push(e); } } return Array.from(map.entries()).map(([filePath, edits]) => ({ filePath, edits })); } function applyFixes(results, directory, dryRun) { const filesWithEdits = collectFixEdits(results); let appliedEditCount = 0; const changedFiles = []; for (const { filePath, edits } of filesWithEdits) { try { const abs = (0, path_1.join)(directory, filePath); const original = (0, fs_1.readFileSync)(abs, 'utf-8'); const updated = applyEditsToContent(original, edits); if (updated !== original) { if (!dryRun) (0, fs_1.writeFileSync)(abs, updated, 'utf-8'); appliedEditCount += edits.length; changedFiles.push(filePath); } } catch { // skip file if cannot read/write } } return { changedFiles, appliedEditCount }; } function applyEditsToContent(content, edits) { // Convert line/column to absolute indices, and apply in reverse order const lineStarts = computeLineStarts(content); const normalized = edits .map(e => ({ start: positionToIndex(e.startLine, e.startColumn, lineStarts), end: positionToIndex(e.endLine, e.endColumn, lineStarts), replacement: e.replacement })) .sort((a, b) => b.start - a.start); let updated = content; for (const e of normalized) { updated = updated.slice(0, e.start) + e.replacement + updated.slice(e.end); } return updated; } function computeLineStarts(text) { const starts = [0]; for (let i = 0; i < text.length; i++) { if (text[i] === '\n') starts.push(i + 1); } return starts; } function positionToIndex(line, column, lineStarts) { const lineIndex = Math.max(1, line) - 1; const lineStart = lineStarts[Math.min(lineIndex, lineStarts.length - 1)] ?? 0; return lineStart + Math.max(0, column - 1); } /** * Generate a preview of fixes without applying them */ function previewFixes(results, directory) { const previews = []; const filesWithEdits = collectFixEdits(results); for (const { filePath, edits } of filesWithEdits) { try { const abs = (0, path_1.join)(directory, filePath); const content = (0, fs_1.readFileSync)(abs, 'utf-8'); const lines = content.split('\n'); for (const edit of edits) { const lineIndex = edit.startLine - 1; if (lineIndex < 0 || lineIndex >= lines.length) continue; const beforeLine = lines[lineIndex]; // Find the result that generated this edit const result = results.find(r => r.fixEdits?.some(e => e.file === edit.file && e.startLine === edit.startLine)); // Compute what the line would look like after the fix let afterLine = beforeLine; if (edit.startLine === edit.endLine) { // Single-line edit const startCol = Math.max(0, edit.startColumn - 1); const endCol = Math.max(startCol, edit.endColumn - 1); afterLine = beforeLine.slice(0, startCol) + edit.replacement + beforeLine.slice(endCol); } else { // Multi-line edit - show just the replacement afterLine = edit.replacement.split('\n')[0] || '(multi-line change)'; } previews.push({ file: filePath, ruleId: result?.ruleId || 'unknown', line: edit.startLine, before: beforeLine.trim(), after: afterLine.trim() }); } } catch { // Skip files that can't be read } } return previews; } /** * Print fix previews in a diff-like format */ function printFixPreviews(previews) { if (previews.length === 0) { console.log('No auto-fixable issues found.'); return; } console.log(`\nšŸ”§ Fix Preview (${previews.length} changes)\n`); console.log('The following changes would be applied:\n'); // Group by file const byFile = new Map(); for (const p of previews) { if (!byFile.has(p.file)) byFile.set(p.file, []); byFile.get(p.file).push(p); } for (const [file, filePreview] of byFile) { console.log(`šŸ“„ ${file}`); for (const p of filePreview) { console.log(` Line ${p.line} [${p.ruleId}]:`); console.log(` - ${p.before}`); console.log(` + ${p.after}`); console.log(''); } } console.log('Run with --apply-fixes to apply these changes.'); } //# sourceMappingURL=fix.js.map