vulnerability_scan
Scan dependencies for known vulnerabilities using OSV.dev data. Supports multiple ecosystems with no configuration required.
Instructions
Scan dependencies for known CVEs via OSV.dev — zero config, multi-ecosystem
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- Main handler for vulnerability_scan. Receives db, params, and liveIntelligence; checks if live intelligence is enabled/initialized, calls liveIntelligence.scanVulnerabilities(), and returns formatted results with severity filtering.
export async function executeVulnerabilityScan( db: FourDADatabase, params: VulnerabilityScanParams, liveIntelligence: LiveIntelligence | null, ) { if (!liveIntelligence || !liveIntelligence.isEnabled()) { return { error: null, offline: true, message: "Live intelligence disabled. Set FOURDA_OFFLINE=false or remove the env var to enable vulnerability scanning.", hint: "4DA queries OSV.dev with your dependency names and versions (public manifest data). No personal data is transmitted.", }; } if (!liveIntelligence.isInitialized()) { return { error: null, offline: false, message: "No project dependencies detected. Ensure manifest files (package.json, Cargo.toml, etc.) exist in your project.", }; } const projectPath = params.project_path || process.cwd(); const result = await liveIntelligence.scanVulnerabilities(projectPath, { includeDev: params.include_dev || false, forceRefresh: params.force_refresh || false, }); if (result.totalScanned === 0 && !result.offline) { const hint = diagnoseMissingVersions(projectPath); return { ...formatResult(result, params.severity_filter), note: hint, }; } return formatResult(result, params.severity_filter); } - Input schema & tool definition for vulnerability_scan. Defines VulnerabilityScanParams interface and the tool object with name 'vulnerability_scan', description, and inputSchema with fields: project_path, severity_filter, include_dev, force_refresh.
export interface VulnerabilityScanParams { project_path?: string; severity_filter?: "critical" | "high" | "medium" | "low"; include_dev?: boolean; force_refresh?: boolean; } export const vulnerabilityScanTool = { name: "vulnerability_scan", description: "Scan project dependencies for known vulnerabilities (CVEs) using OSV.dev. " + "Returns severity, fix versions, and upgrade recommendations for npm, Rust, Python, and Go. " + "Zero config — automatically detects your stack from manifest/lock files. " + "Privacy: only sends package names and versions (public manifest data).", inputSchema: { type: "object" as const, properties: { project_path: { type: "string", description: "Project directory to scan. Default: current working directory.", }, severity_filter: { type: "string", enum: ["critical", "high", "medium", "low"], description: "Only show vulnerabilities at or above this severity level.", }, include_dev: { type: "boolean", description: "Include devDependencies in scan. Default: false.", }, force_refresh: { type: "boolean", description: "Ignore cache and fetch fresh data from OSV.dev. Default: false.", }, }, }, }; - mcp-4da-server/src/tool-dispatch.ts:40-43 (registration)Dispatch registration: maps 'vulnerability_scan' to executeVulnerabilityScan with liveIntelligence injected from singleton.
const DISPATCH_MAP: Record<string, ToolExecutor> = { // Dependency Security vulnerability_scan: (db, params) => executeVulnerabilityScan(db, params, getLiveIntelligence()), dependency_health: (db, params) => executeDependencyHealth(db, params, getLiveIntelligence()), - mcp-4da-server/src/schema-registry.ts:38-46 (registration)Schema registry entry: registers vulnerability_scan with summary, category 'security', tags, and standalone flag.
export const TOOL_REGISTRY: Record<string, ToolRegistryEntry> = { // --- Dependency Security (standalone) --- vulnerability_scan: { summary: "Scan dependencies for known CVEs via OSV.dev — zero config, multi-ecosystem", schemaFile: "vulnerability-scan.json", category: "security", tags: ["security", "vulnerabilities", "cve", "dependencies", "osv"], standalone: true, }, - mcp-4da-server/src/tools/index.ts:12-16 (registration)Tool barrel export: re-exports vulnerabilityScanTool and executeVulnerabilityScan from vulnerability-scan.ts.
// Security export { vulnerabilityScanTool, executeVulnerabilityScan, } from "./vulnerability-scan.js"; - OsvScanner class that performs the actual OSV.dev API batch query, caching, and vulnerability mapping. Called by LiveIntelligence.scanVulnerabilities().
export class OsvScanner { private cache: LiveCache; private rateLimiter: RateLimiter; constructor(cache: LiveCache, rateLimiter: RateLimiter) { this.cache = cache; this.rateLimiter = rateLimiter; } async scan(deps: ResolvedDependency[], projectPath: string): Promise<VulnerabilityScanResult> { const start = Date.now(); const scannable = deps.filter((d) => d.version !== null).slice(0, MAX_BATCH_SIZE); const ecosystems = [...new Set(scannable.map((d) => d.ecosystem))]; // Check cache for each dep individually const uncached: ResolvedDependency[] = []; const cachedVulns: VulnerabilityEntry[] = []; for (const dep of scannable) { const cacheKey = `osv:${dep.ecosystem}:${dep.name}:${dep.version}`; const cached = this.cache.get<VulnerabilityEntry[]>(cacheKey); if (cached !== null) { cachedVulns.push(...cached); } else { uncached.push(dep); } } // Fetch uncached from OSV let fetchedVulns: VulnerabilityEntry[] = []; let offline = false; if (uncached.length > 0) { if (!this.rateLimiter.canProceed("osv")) { offline = true; } else { try { fetchedVulns = await this.batchQuery(uncached); this.rateLimiter.consume("osv"); } catch { offline = true; // Try stale cache for uncached deps for (const dep of uncached) { const cacheKey = `osv:${dep.ecosystem}:${dep.name}:${dep.version}`; const stale = this.cache.getStale<VulnerabilityEntry[]>(cacheKey); if (stale) cachedVulns.push(...stale.data); } } } } const allVulns = [...cachedVulns, ...fetchedVulns]; const vulnerablePackages = new Set(allVulns.map((v) => v.package)); const bySeverity = { critical: 0, high: 0, medium: 0, low: 0, unknown: 0 }; for (const v of allVulns) { bySeverity[v.severity]++; } return { scannedAt: new Date().toISOString(), projectPath, ecosystemsScanned: ecosystems, totalScanned: scannable.length, totalVulnerable: vulnerablePackages.size, bySeverity, vulnerabilities: allVulns, cleanCount: scannable.length - vulnerablePackages.size, scanDurationMs: Date.now() - start, cached: uncached.length === 0, offline, }; } private async batchQuery(deps: ResolvedDependency[]): Promise<VulnerabilityEntry[]> { const queries: OsvBatchQuery[] = deps.map((d) => ({ package: { name: d.name, ecosystem: d.ecosystem }, ...(d.version ? { version: d.version } : {}), })); const response = await fetchWithTimeout(OSV_BATCH_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ queries }), }, OSV_TIMEOUT_MS); if (!response.ok) { throw new Error(`OSV API error: ${response.status}`); } const data = (await response.json()) as OsvBatchResponse; const results: VulnerabilityEntry[] = []; for (let i = 0; i < data.results.length; i++) { const dep = deps[i]; const osvResult = data.results[i]; const depVulns: VulnerabilityEntry[] = []; if (osvResult.vulns && osvResult.vulns.length > 0) { for (const vuln of osvResult.vulns) { depVulns.push(mapVulnerability(vuln, dep)); } } // Cache per-dep (even empty results to avoid re-fetching clean deps) const cacheKey = `osv:${dep.ecosystem}:${dep.name}:${dep.version}`; this.cache.set(cacheKey, depVulns, "osv", OSV_CACHE_TTL); results.push(...depVulns); } return results; } } - VulnerabilityScanResult type definition with all output fields: scannedAt, projectPath, ecosystemsScanned, totalScanned, totalVulnerable, bySeverity, vulnerabilities, cleanCount, scanDurationMs, cached, offline.
export interface VulnerabilityScanResult { scannedAt: string; projectPath: string; ecosystemsScanned: string[]; totalScanned: number; totalVulnerable: number; bySeverity: { critical: number; high: number; medium: number; low: number; unknown: number }; vulnerabilities: VulnerabilityEntry[]; cleanCount: number; scanDurationMs: number; cached: boolean; offline: boolean; }