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.
105 lines • 4.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ViteScanner = void 0;
const glob_1 = require("glob");
const fs_1 = require("fs");
const path_1 = require("path");
const rules_1 = require("../rules");
class ViteScanner {
name = 'Vite Security Scanner';
async scan(options) {
const results = [];
// Check if this is a Vite project
const viteConfigExists = (0, fs_1.existsSync)((0, path_1.join)(options.directory, 'vite.config.ts')) ||
(0, fs_1.existsSync)((0, path_1.join)(options.directory, 'vite.config.js'));
const files = await (0, glob_1.glob)('**/*.{js,jsx,ts,tsx,svelte}', {
cwd: options.directory,
ignore: ['node_modules/**', 'dist/**', 'build/**', '.next/**', 'examples/**', 'vite.config.*']
});
// If no vite config, check if any files use Vite features
if (!viteConfigExists) {
let hasViteUsage = false;
for (const file of files) {
try {
const filePath = `${options.directory}/${file}`;
const content = (0, fs_1.readFileSync)(filePath, 'utf-8');
if (/import\.meta\.(env|glob|hot)|import\s*\(/i.test(content)) {
hasViteUsage = true;
break;
}
}
catch { }
}
if (!hasViteUsage) {
return results; // Skip if not a Vite project
}
}
// Vite-specific rules
const viteRuleIds = [
'VITE001', // Client-Side Environment Variable Exposure
'VITE002', // Development-Only Code
'VITE003' // Unsafe Dynamic Imports
];
for (const file of files) {
try {
const filePath = `${options.directory}/${file}`;
const content = (0, fs_1.readFileSync)(filePath, 'utf-8');
const lines = content.split('\n');
// Check if file uses Vite-specific features
const hasViteUsage = /import\.meta\.(env|glob|hot)|import\s*\(/i.test(content);
// If no vite.config, only scan files with Vite usage
if (!viteConfigExists && !hasViteUsage) {
continue;
}
// Skip non-Vite files in node_modules
if (!hasViteUsage && file.includes('node_modules')) {
continue;
}
// Apply each Vite rule
for (const ruleId of viteRuleIds) {
const rule = (0, rules_1.getRule)(ruleId);
if (!rule || !rule.impl.detect)
continue;
// Check file type restrictions
if (rule.impl.fileTypes) {
const fileExt = file.split('.').pop() || '';
if (!rule.impl.fileTypes.includes(fileExt))
continue;
}
// Run detection
const detections = rule.impl.detect(content, file, lines);
// Convert detections to ScanResults
for (const detection of detections) {
const line = lines[detection.line - 1] || '';
results.push({
type: rule.meta.severity === 'high' ? 'error' : 'warning',
category: rule.meta.category,
message: rule.meta.message,
file,
line: detection.line,
range: {
startLine: detection.line,
startColumn: 1,
endLine: detection.line,
endColumn: Math.max(1, line.length)
},
severity: rule.meta.severity,
ruleId: rule.meta.id,
confidence: detection.confidence || 0.85,
match: detection.match,
fix: rule.meta.fix,
helpUri: rule.meta.helpUri,
...(detection.fixEdits ? { fixEdits: detection.fixEdits } : {})
});
}
}
}
catch (error) {
// Skip files that can't be read
}
}
return results;
}
}
exports.ViteScanner = ViteScanner;
//# sourceMappingURL=vite-scanner.js.map