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.
88 lines • 4.44 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const rule = {
meta: {
id: 'VITE003',
category: 'security',
severity: 'high',
message: 'Unsafe dynamic import with user input - potential path traversal',
fix: 'Use a whitelist of allowed module names before dynamic import',
helpUri: 'https://vitejs.dev/guide/features.html#dynamic-import',
impact: 'Unvalidated dynamic imports can allow attackers to load arbitrary modules or traverse the file system.'
},
impl: {
detect: (content, file, lines) => {
const results = [];
// Pattern: import() with template literals containing variables
const dynamicImportPattern = /import\s*\(\s*`[^`]*\$\{[^}]+\}[^`]*`\s*\)/gi;
let match;
while ((match = dynamicImportPattern.exec(content)) !== null) {
const beforeMatch = content.substring(0, match.index);
const lineNumber = beforeMatch.split('\n').length;
// Check if there's validation nearby (within 3 lines before)
const startCheck = Math.max(0, lineNumber - 4);
const contextLines = lines.slice(startCheck, lineNumber);
const hasValidation = contextLines.some(l => /whitelist|allowed|includes\(|indexOf\(|switch\s*\(/i.test(l) ||
/\b(validate|check|verify).*module/i.test(l));
if (!hasValidation) {
results.push({
line: lineNumber,
match: match[0].slice(0, 100),
confidence: 0.88
});
}
}
// Pattern: import() with string concatenation
const concatImportPattern = /import\s*\(\s*['"][^'"]*['"]\s*\+[^)]+\)/gi;
while ((match = concatImportPattern.exec(content)) !== null) {
const beforeMatch = content.substring(0, match.index);
const lineNumber = beforeMatch.split('\n').length;
// Check if there's validation nearby (within 3 lines before)
const startCheck = Math.max(0, lineNumber - 4);
const contextLines = lines.slice(startCheck, lineNumber);
const hasValidation = contextLines.some(l => /whitelist|allowed|includes\(|indexOf\(|switch\s*\(/i.test(l) ||
/\b(validate|check|verify).*module/i.test(l));
if (!hasValidation) {
results.push({
line: lineNumber,
match: match[0].slice(0, 100),
confidence: 0.88
});
}
}
// Pattern: import.meta.glob with user input
const globPattern = /import\.meta\.glob\s*\(\s*`[^`]*\$\{[^}]+\}[^`]*`\s*\)/gi;
while ((match = globPattern.exec(content)) !== null) {
const beforeMatch = content.substring(0, match.index);
const lineNumber = beforeMatch.split('\n').length;
results.push({
line: lineNumber,
match: match[0].slice(0, 100),
confidence: 0.92
});
}
// Pattern: Check for path traversal attempts in dynamic imports
lines.forEach((line, index) => {
if (/import\s*\(/.test(line) && /\$\{.*\}/.test(line)) {
// Check if path could contain ../ or absolute paths
if (/\.\.|\/\//i.test(line) || /file|path|route/i.test(line)) {
// Check for validation
const contextLines = lines.slice(Math.max(0, index - 3), index);
const hasValidation = contextLines.some(l => /normalize|resolve|basename|sanitize/i.test(l));
if (!hasValidation) {
results.push({
line: index + 1,
match: line.trim().slice(0, 100),
confidence: 0.85
});
}
}
}
});
return results;
},
fileTypes: ['js', 'jsx', 'ts', 'tsx', 'svelte', 'astro']
}
};
exports.default = rule;
//# sourceMappingURL=VITE003.js.map