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.

102 lines 4.14 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseScanner = void 0; const glob_1 = require("glob"); const fs_1 = require("fs"); const path_1 = require("path"); const result_cache_1 = require("../utils/result-cache"); const file_source_cache_1 = require("../utils/file-source-cache"); class BaseScanner { resultCache = null; initCache(options, signature) { this.resultCache = options.noResultCache ? null : new result_cache_1.ResultCache(options.directory, signature); } getCached(file, contentHash) { return this.resultCache?.get(file, contentHash) ?? null; } setCached(file, contentHash, results) { this.resultCache?.set(file, contentHash, results); } saveCache() { this.resultCache?.save(); } async *iterateFiles(options, pattern, ignore) { const files = await (0, glob_1.glob)(pattern, { cwd: options.directory, // dot:true so files like .cursor/mcp.json or .env.local are reachable; // we still ignore noisy hidden dirs (.git, .next, etc.) explicitly. dot: true, ignore: [ '.git/**', '.next/**', '.svelte-kit/**', '.turbo/**', '.cache/**', '.parcel-cache/**', '.nuxt/**', '.output/**', '.vercel/**', '.netlify/**', // Build / coverage artifacts — HTML, .js.map, compiled output. These // aren't source and produce massive A11Y / noise if scanned. 'coverage/**', 'dist/**', 'build/**', 'out/**', '.ubon/**', ...ignore, ...(options.exclude || []) ] }); const maxSize = options.maxFileSize || file_source_cache_1.DEFAULT_MAX_FILE_SIZE; const cache = file_source_cache_1.FileSourceCache.forDirectory(options.directory); for (const file of files) { const absolute = (0, path_1.join)(options.directory, file); try { const stat = (0, fs_1.statSync)(absolute); if (stat.size > maxSize) { if (options.verbose) { console.error(`🪷 ${this.name}: skipping ${file} (size ${stat.size} > ${maxSize} bytes)`); } continue; } const content = cache.read(absolute) ?? (0, fs_1.readFileSync)(absolute, 'utf-8'); const contentHash = result_cache_1.ResultCache.hashContent(content); yield { file, content, lines: content.split('\n'), contentHash }; } catch (error) { if (options.verbose) { console.error(`🪷 ${this.name}: failed to read ${file}:`, error); } } } } hasFileSuppression(lines) { return lines.some((line) => /ubon-disable-file/.test(line)); } isSuppressed(lines, lineIndex, ruleId) { const disableNext = /ubon-disable-next-line\s+([A-Z0-9_,\s-]+)/.exec(lines[lineIndex] || ''); const prevDisable = lineIndex > 0 ? /ubon-disable-next-line\s+([A-Z0-9_,\s-]+)/.exec(lines[lineIndex - 1]) : null; const disabledList = new Set([ ...(disableNext && disableNext[1] ? disableNext[1].split(/[,\s]+/).filter(Boolean) : []), ...(prevDisable && prevDisable[1] ? prevDisable[1].split(/[,\s]+/).filter(Boolean) : []) ]); return disabledList.has(ruleId); } createResult(partial, lineText) { const range = partial.range || (partial.line ? { startLine: partial.line, startColumn: 1, endLine: partial.line, endColumn: Math.max(1, (lineText || '').length) } : undefined); return { ...partial, range }; } } exports.BaseScanner = BaseScanner; //# sourceMappingURL=base-scanner.js.map