UNPKG

vulnzap-mcp

Version:

Multi-ecosystem vulnerability scanning service with MCP interface for LLMs

250 lines (212 loc) 6.75 kB
/** * Vulnzap Server * * Multi-ecosystem vulnerability scanning server with MCP interface for LLMs. * Supports npm, pip, Go, and other ecosystems with a comprehensive vulnerability database. */ import express from 'express'; import cors from 'cors'; import morgan from 'morgan'; import dotenv from 'dotenv'; import path from 'path'; import fs from 'fs/promises'; import { fileURLToPath } from 'url'; import CONFIG from './core/config.js'; import mcpRoutes from './api/mcp-routes.js'; // Load environment variables dotenv.config(); // Convert import.meta.url to __dirname equivalent for ES modules const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Create Express app const app = express(); const PORT = process.env.PORT || 3001; // Changed default from 3000 to 3001 const MAX_PORT_ATTEMPTS = 10; // Try up to 10 different ports let currentPort = PORT; // Create required directories (async () => { try { // Create cache directory if it doesn't exist await fs.mkdir(CONFIG.DATA_PATHS.CACHE_DIR, { recursive: true }); console.log(`Created cache directory at: ${CONFIG.DATA_PATHS.CACHE_DIR}`); // Create data directory if it doesn't exist await fs.mkdir(CONFIG.DATA_PATHS.DATA_DIR, { recursive: true }); console.log(`Created data directory at: ${CONFIG.DATA_PATHS.DATA_DIR}`); } catch (err) { console.error('Error creating directories:', err); } })(); // Middleware app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); // Logging if (process.env.NODE_ENV !== 'production') { app.use(morgan('dev')); } else { app.use(morgan('combined')); } // Request size limits app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true, limit: '10mb' })); // Basic request logging middleware app.use((req, res, next) => { const start = Date.now(); res.on('finish', () => { const duration = Date.now() - start; console.log(`${req.method} ${req.originalUrl} ${res.statusCode} ${duration}ms`); }); next(); }); // Rate limiting middleware const rateLimiter = (windowMs, maxRequests) => { const clients = new Map(); return (req, res, next) => { const clientIP = req.ip; // Get current client data or initialize const clientData = clients.get(clientIP) || { count: 0, resetTime: Date.now() + windowMs }; // Check if window expired and reset if (Date.now() > clientData.resetTime) { clientData.count = 0; clientData.resetTime = Date.now() + windowMs; } // Increment count clientData.count++; // Update map clients.set(clientIP, clientData); // Check limit if (clientData.count > maxRequests) { return res.status(429).json({ error: 'Rate limit exceeded', retryAfter: Math.ceil((clientData.resetTime - Date.now()) / 1000) }); } next(); }; }; // Apply rate limiting for API endpoints app.use('/api', rateLimiter( CONFIG.RATE_LIMITING.WINDOW_MS, CONFIG.RATE_LIMITING.MAX_REQUESTS )); // Routes app.use('/mcp', mcpRoutes); // API routes (if enabled) if (CONFIG.SERVER.ENABLE_API) { try { const apiRoutes = await import('./api/api-routes.js'); app.use('/api', apiRoutes.default); console.log('API routes enabled'); } catch (err) { console.error('Error loading API routes:', err); } } // Web UI routes (if enabled) if (CONFIG.SERVER.ENABLE_WEB) { try { const webRoutes = await import('./web/web-routes.js'); app.use('/', webRoutes.default); console.log('Web UI routes enabled'); } catch (err) { console.error('Error loading web routes:', err); } } // Default route app.get('/', (req, res) => { res.json({ name: 'Vulnzap Vulnerability Scanner', version: '2.0.0', description: 'Multi-ecosystem vulnerability scanning server with MCP interface', endpoints: { mcp: '/mcp - Model Context Protocol interface', api: '/api - REST API endpoints (if enabled)', web: '/ - Web interface (if enabled)' }, supportedEcosystems: CONFIG.ENABLED_ECOSYSTEMS, docs: 'https://github.com/example/vulnzap' }); }); // Error handling middleware app.use((err, req, res, next) => { console.error('Server error:', err); res.status(500).json({ error: 'Internal server error', message: process.env.NODE_ENV === 'production' ? 'An unexpected error occurred' : err.message }); }); // Not found handler app.use((req, res) => { res.status(404).json({ error: 'Not found', message: `The requested resource at ${req.originalUrl} was not found` }); }); // Start server function startServer(port) { return new Promise((resolve, reject) => { const serverInstance = app.listen(port) .on('error', (err) => { if (err.code === 'EADDRINUSE') { console.log(`Port ${port} is already in use, trying next port...`); reject(err); } else { console.error('Server error:', err); reject(err); } }) .on('listening', () => { console.log(`Vulnzap server running on port ${port}`); console.log(`Supported ecosystems: ${CONFIG.ENABLED_ECOSYSTEMS.join(', ')}`); console.log(`MCP endpoint: http://localhost:${port}/mcp`); if (CONFIG.SERVER.ENABLE_API) { console.log(`API endpoint: http://localhost:${port}/api`); } if (CONFIG.SERVER.ENABLE_WEB) { console.log(`Web UI: http://localhost:${port}`); } resolve(serverInstance); }); }); } // Try ports sequentially until one works async function findAvailablePort() { let attempts = 0; let server; while (attempts < MAX_PORT_ATTEMPTS) { try { server = await startServer(currentPort); break; // Successfully started the server } catch (err) { if (err.code === 'EADDRINUSE') { attempts++; currentPort++; // Try the next port } else { throw err; // Re-throw other errors } } } if (!server) { throw new Error(`Failed to find an available port after ${MAX_PORT_ATTEMPTS} attempts`); } return server; } // Start the server with port-finding logic const server = await findAvailablePort(); // Graceful shutdown process.on('SIGTERM', gracefulShutdown); process.on('SIGINT', gracefulShutdown); function gracefulShutdown() { console.log('Received shutdown signal, closing server...'); server.close(() => { console.log('Server closed'); process.exit(0); }); // Force close after timeout setTimeout(() => { console.error('Could not close connections in time, forcing shutdown'); process.exit(1); }, 10000); }