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
JavaScript
;
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