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.

144 lines 5.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.OSVScanner = void 0; const fs_1 = require("fs"); const https_1 = __importDefault(require("https")); const cache_1 = require("../utils/cache"); function postJson(url, payload) { return new Promise((resolve, reject) => { const { hostname, pathname } = new URL(url); const data = JSON.stringify(payload); const req = https_1.default.request({ hostname, path: pathname, method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) } }, (res) => { let body = ''; res.setEncoding('utf8'); res.on('data', chunk => body += chunk); res.on('end', () => { if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { try { resolve(JSON.parse(body)); } catch (e) { resolve({}); } } else { reject(new Error(`OSV ${res.statusCode}`)); } }); }); req.on('error', reject); req.write(data); req.end(); }); } class OSVScanner { name = 'Dependency Advisory Scanner'; cache = new cache_1.FileCache('osv'); async scan(options) { const results = []; // Clear cache if requested if (options.clearCache) { this.cache.clear(); } const npmPath = `${options.directory}/package.json`; const pyPath = `${options.directory}/requirements.txt`; const npmDeps = []; if ((0, fs_1.existsSync)(npmPath)) { try { const pkg = JSON.parse((0, fs_1.readFileSync)(npmPath, 'utf-8')); const all = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) }; for (const [name, ver] of Object.entries(all)) { const clean = String(ver).replace(/^[^0-9]*/, ''); if (clean) npmDeps.push({ name, version: clean }); } } catch { } } const pyDeps = []; if ((0, fs_1.existsSync)(pyPath)) { try { const text = (0, fs_1.readFileSync)(pyPath, 'utf-8'); for (const line of text.split('\n')) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; const [nameVer] = trimmed.split(/\s+/); const [name, version] = nameVer.split('=='); if (name) pyDeps.push({ name, version }); } } catch { } } const queries = []; for (const d of npmDeps) { queries.push({ package: { ecosystem: 'npm', name: d.name }, version: d.version }); } for (const d of pyDeps) { queries.push({ package: { ecosystem: 'PyPI', name: d.name }, version: d.version }); } if (queries.length === 0) return results; // Create cache key for this set of queries const cacheKey = (0, cache_1.createOSVCacheKey)(queries); // Try to get cached results first (unless caching is disabled) let data = options.noCache ? null : this.cache.get(cacheKey); if (!data) { // No cached data, make API call try { data = await postJson('https://api.osv.dev/v1/querybatch', { queries }); // Cache the results for 24 hours (unless caching is disabled) if (!options.noCache) { this.cache.set(cacheKey, data, cache_1.CACHE_TTL.OSV_VULNERABILITIES); } } catch (error) { // API call failed, return empty results return results; } } try { const vulns = data.results || []; const buckets = new Map(); vulns.forEach((entry, idx) => { const q = queries[idx]; if (!entry?.vulns?.length) return; const key = `${q.package.ecosystem}:${q.package.name}`; let bucket = buckets.get(key); if (!bucket) { bucket = { eco: q.package.ecosystem, name: q.package.name, version: q.version || '', ids: [] }; buckets.set(key, bucket); } for (const v of entry.vulns) { const id = v.id || v.aliases?.[0]; if (id && !bucket.ids.includes(id)) bucket.ids.push(id); } }); for (const { eco, name, version, ids } of buckets.values()) { const count = ids.length; const preview = ids.slice(0, 3).join(', '); const suffix = count > 3 ? `, +${count - 3} more` : ''; results.push({ type: 'error', category: 'security', message: `${count} known vulnerabilit${count === 1 ? 'y' : 'ies'} in ${eco}:${name}${version ? `@${version}` : ''} (${preview}${suffix})`, severity: 'high', ruleId: 'OSV001', fix: `Upgrade ${name} to a patched version`, confidence: 0.9, match: ids.join(', ') }); } } catch { } return results; } } exports.OSVScanner = OSVScanner; //# sourceMappingURL=osv-scanner.js.map