UNPKG

mcp-cve-intelligence-server-lite-test

Version:

Lite Model Context Protocol server for comprehensive CVE intelligence gathering with multi-source exploit discovery, designed for security professionals and cybersecurity researchers - Alpha Release

432 lines 17.5 kB
/** * Centralized Configuration Management System * Single source of truth for all application configuration */ import fs from 'fs'; import path from 'path'; import { VERSION, PACKAGE_NAME } from '../version.js'; // Environment Variable Management class EnvironmentManager { static instance; constructor() { } static getInstance() { if (!EnvironmentManager.instance) { EnvironmentManager.instance = new EnvironmentManager(); } return EnvironmentManager.instance; } get(name, defaultValue) { return process.env[name] ?? defaultValue; } getRequired(name) { const value = process.env[name]; if (!value) { throw new Error(`Required environment variable ${name} is not set`); } return value; } getNumber(name, defaultValue) { const value = process.env[name]; if (!value) { return defaultValue; } const parsed = parseFloat(value); if (isNaN(parsed)) { // eslint-disable-next-line no-console -- Configuration warnings must be visible during startup console.warn(`Invalid value for ${name}: ${value}. Using default: ${defaultValue}`); return defaultValue; } return parsed; } getBoolean(name, defaultValue) { const value = process.env[name]; if (!value) { return defaultValue; } const lower = value.toLowerCase(); if (lower === 'true' || lower === '1' || lower === 'yes') { return true; } if (lower === 'false' || lower === '0' || lower === 'no') { return false; } // eslint-disable-next-line no-console -- Configuration warnings must be visible during startup console.warn(`Invalid value for ${name}: ${value}. Using default: ${defaultValue}`); return defaultValue; } getEnum(name, validValues, defaultValue) { const value = process.env[name]; if (!value) { return defaultValue; } if (validValues.includes(value)) { return value; } // eslint-disable-next-line no-console -- Configuration warnings must be visible during startup console.warn(`Invalid value for ${name}: ${value}. Using default: ${defaultValue}`); return defaultValue; } // Set environment variables (used by CLI) set(name, value) { process.env[name] = value; } // Set only if not already set (CLI options don't override env vars) setIfNotSet(name, value) { if (!process.env[name]) { process.env[name] = value; } } } // Configuration Builder class ConfigurationBuilder { env; sourcesConfigCache = null; constructor() { this.env = EnvironmentManager.getInstance(); } loadSourcesConfigFile() { if (this.sourcesConfigCache) { return this.sourcesConfigCache; } const sourcesPath = path.join(process.cwd(), 'cve-sources.json'); if (!fs.existsSync(sourcesPath)) { throw new Error(`Sources configuration file not found: ${sourcesPath}`); } try { const configData = fs.readFileSync(sourcesPath, 'utf8'); this.sourcesConfigCache = JSON.parse(configData); return this.sourcesConfigCache; } catch (error) { if (error instanceof SyntaxError) { throw new Error(`Invalid JSON in sources configuration: ${error.message}`); } throw error; } } buildServerConfig() { return { name: PACKAGE_NAME, version: VERSION, environment: this.env.getEnum('NODE_ENV', ['development', 'production', 'test'], 'development'), processTitle: `${PACKAGE_NAME}-${VERSION}`, }; } buildLoggingConfig() { return { level: this.env.getEnum('LOG_LEVEL', ['error', 'warn', 'info', 'debug'], 'info'), dir: this.env.get('LOG_DIR', 'logs') ?? 'logs', maxFiles: this.env.getNumber('MAX_LOG_FILES', 5), maxFileSizeMB: this.env.getNumber('MAX_LOG_FILE_SIZE_MB', 50), enableConsole: this.env.getBoolean('LOG_ENABLE_CONSOLE', true), enableFile: this.env.getBoolean('LOG_ENABLE_FILE', true), }; } buildTransportConfig() { return { type: this.env.getEnum('MCP_TRANSPORT_TYPE', ['stdio', 'http'], 'stdio'), http: { port: this.env.getNumber('MCP_HTTP_PORT', 3001), host: this.env.get('MCP_HTTP_HOST', '0.0.0.0') ?? '0.0.0.0', enableCors: this.env.getBoolean('MCP_HTTP_CORS', true), compressJson: this.env.getBoolean('MCP_JSON_COMPRESS', true), sessionManagement: this.env.getBoolean('MCP_HTTP_SESSION_MANAGEMENT', true), }, }; } buildSecurityConfig() { // Load API keys dynamically based on source configuration const apiKeys = {}; const rateLimits = { default: { requests: this.env.getNumber('RATE_LIMIT_DEFAULT_REQUESTS', 100), windowMs: this.env.getNumber('RATE_LIMIT_DEFAULT_WINDOW_MS', 60000), }, }; // Load sources to get their API key configurations try { const config = this.loadSourcesConfigFile(); if (config.sources) { // Dynamically load API keys and rate limits based on source configurations for (const [sourceKey, sourceConfig] of Object.entries(config.sources)) { const source = sourceConfig; // Load API key if source requires one if (source.apiKeyEnvVar) { apiKeys[sourceKey] = this.env.get(source.apiKeyEnvVar); } // Set up rate limits for each source rateLimits[sourceKey] = { requests: this.env.getNumber(`RATE_LIMIT_${sourceKey.toUpperCase()}_REQUESTS`, 100), windowMs: this.env.getNumber(`RATE_LIMIT_${sourceKey.toUpperCase()}_WINDOW_MS`, 60000), }; } } } catch { // If sources config fails to load, fall back to common API keys and rate limits // This ensures the security config can still be built even if sources config is missing apiKeys.nvd = this.env.get('NVD_API_KEY'); apiKeys.github = this.env.get('GITHUB_TOKEN'); apiKeys.mitre = this.env.get('MITRE_API_KEY'); rateLimits.nvd = { requests: this.env.getNumber('RATE_LIMIT_NVD_REQUESTS', 50), windowMs: this.env.getNumber('RATE_LIMIT_NVD_WINDOW_MS', 1000), }; rateLimits.github = { requests: this.env.getNumber('RATE_LIMIT_GITHUB_REQUESTS', 5000), windowMs: this.env.getNumber('RATE_LIMIT_GITHUB_WINDOW_MS', 3600000), }; rateLimits.mitre = { requests: this.env.getNumber('RATE_LIMIT_MITRE_REQUESTS', 100), windowMs: this.env.getNumber('RATE_LIMIT_MITRE_WINDOW_MS', 60000), }; } // Add any additional API keys that might not be source-specific const additionalKeys = { mitreOrg: this.env.get('MITRE_API_ORG') || this.env.get('CVE_API_ORG'), mitreUser: this.env.get('MITRE_API_USER') || this.env.get('CVE_API_USER'), }; Object.assign(apiKeys, additionalKeys); return { apiKeys, rateLimits: rateLimits, }; } buildPerformanceConfig() { return { cache: { enabled: this.env.getBoolean('CACHE_ENABLED', true), ttlSeconds: this.env.getNumber('CACHE_TTL_SECONDS', 3600), maxSize: this.env.getNumber('CACHE_MAX_SIZE', 1000), }, timeouts: { default: this.env.getNumber('TIMEOUT_DEFAULT', 30000), search: this.env.getNumber('TIMEOUT_SEARCH', 15000), details: this.env.getNumber('TIMEOUT_DETAILS', 10000), }, concurrency: { maxConcurrentRequests: this.env.getNumber('MAX_CONCURRENT_REQUESTS', 10), requestBatchSize: this.env.getNumber('REQUEST_BATCH_SIZE', 5), }, }; } buildSourcesConfig() { const config = this.loadSourcesConfigFile(); if (!config.sources) { throw new Error('Sources configuration missing required "sources" section'); } // Enhance source configs with environment-specific overrides const sources = {}; for (const [key, source] of Object.entries(config.sources)) { const sourceConfig = source; sources[key] = { ...sourceConfig, enabled: this.env.getBoolean(`${key.toUpperCase()}_ENABLED`, sourceConfig.enabled), requestTimeout: this.env.getNumber(`${key.toUpperCase()}_TIMEOUT`, sourceConfig.requestTimeout), retryAttempts: this.env.getNumber(`${key.toUpperCase()}_RETRY_ATTEMPTS`, sourceConfig.retryAttempts), retryDelay: this.env.getNumber(`${key.toUpperCase()}_RETRY_DELAY`, sourceConfig.retryDelay), }; } return sources; } buildExploitIndicatorsConfig() { try { const config = this.loadSourcesConfigFile(); if (!config.exploitIndicators) { // eslint-disable-next-line no-console -- Configuration warnings must be visible during startup console.warn('Exploit indicators configuration not found in sources file. Exploit indicators will be disabled.'); return undefined; } const exploitConfig = config.exploitIndicators; // Allow environment override for enabled status const enabled = this.env.getBoolean('EXPLOIT_INDICATORS_ENABLED', exploitConfig.enabled); return { ...exploitConfig, enabled, }; } catch (error) { // eslint-disable-next-line no-console -- Configuration warnings must be visible during startup console.warn(`Failed to load exploit indicators configuration: ${error instanceof Error ? error.message : 'Unknown error'}. Exploit indicators will be disabled.`); return undefined; } } buildEPSSConfig() { try { const config = this.loadSourcesConfigFile(); if (!config.epss) { // eslint-disable-next-line no-console -- Configuration warnings must be visible during startup console.warn('EPSS configuration not found in sources file. Default EPSS configuration will be used.'); return undefined; } return config.epss; } catch (error) { // eslint-disable-next-line no-console -- Configuration warnings must be visible during startup console.warn(`Failed to load EPSS configuration: ${error instanceof Error ? error.message : 'Unknown error'}. Default EPSS configuration will be used.`); return undefined; } } build() { return { server: this.buildServerConfig(), transport: this.buildTransportConfig(), logging: this.buildLoggingConfig(), security: this.buildSecurityConfig(), performance: this.buildPerformanceConfig(), sources: this.buildSourcesConfig(), exploitIndicators: this.buildExploitIndicatorsConfig(), epss: this.buildEPSSConfig(), }; } } // Singleton Configuration Manager class ConfigurationManager { static instance; config = null; builder; constructor() { this.builder = new ConfigurationBuilder(); } static getInstance() { if (!ConfigurationManager.instance) { ConfigurationManager.instance = new ConfigurationManager(); } return ConfigurationManager.instance; } // Load configuration (can be called multiple times to reload) load() { this.config = this.builder.build(); return this.config; } // Get current configuration (loads if not already loaded) get() { if (!this.config) { this.config = this.load(); } return this.config; } // Reload configuration (useful for tests or runtime changes) reload() { this.config = null; return this.load(); } // Get environment manager for direct access getEnvironment() { return EnvironmentManager.getInstance(); } // Validate configuration validate() { const errors = []; try { const config = this.get(); // Validate server config if (!config.server.name || !config.server.version) { errors.push('Server name and version are required'); } // Validate transport config if (config.transport.type === 'http') { if (config.transport.http.port < 1 || config.transport.http.port > 65535) { errors.push('HTTP port must be between 1 and 65535'); } if (!config.transport.http.host) { errors.push('HTTP host is required when using HTTP transport'); } } // Validate logging config - check raw environment value const rawLogLevel = process.env.LOG_LEVEL; if (rawLogLevel && !['error', 'warn', 'info', 'debug'].includes(rawLogLevel)) { errors.push('Invalid logging level'); } // Validate sources if (Object.keys(config.sources).length === 0) { errors.push('At least one source must be configured'); } for (const [name, source] of Object.entries(config.sources)) { if (!source.baseUrl) { errors.push(`Source ${name} missing baseUrl`); } if (source.priority < 1) { errors.push(`Source ${name} priority must be >= 1`); } } } catch (error) { errors.push(`Configuration validation error: ${error instanceof Error ? error.message : 'Unknown error'}`); } return { valid: errors.length === 0, errors, }; } } // Export main functions and classes export const configManager = ConfigurationManager.getInstance(); export const envManager = EnvironmentManager.getInstance(); // CLI helper functions export function setEnvironmentFromCLI(options) { const env = envManager; // Only set if environment variable is not already set if (options.transport) { env.setIfNotSet('MCP_TRANSPORT_TYPE', String(options.transport)); } if (options.port) { env.setIfNotSet('MCP_HTTP_PORT', String(options.port)); } if (options.host) { env.setIfNotSet('MCP_HTTP_HOST', String(options.host)); } if (options.logLevel) { env.setIfNotSet('LOG_LEVEL', String(options.logLevel)); } } // Configuration debugging export function getConfigurationSummary() { const config = configManager.get(); const validation = configManager.validate(); const transportInfo = config.transport.type === 'http' ? ` (${config.transport.http.host}:${config.transport.http.port})` : ''; const enabledSources = Object.values(config.sources).filter(s => s.enabled).length; const apiKeysList = Object.entries(config.security.apiKeys) .filter(([, v]) => v) .map(([k]) => k) .join(', ') || 'none'; const errorsList = validation.errors.length > 0 ? `Errors:\n${validation.errors.map(e => ` - ${e}`).join('\n')}` : ''; const summary = [ 'Configuration Summary:', '===================', `Server: ${config.server.name} v${config.server.version} (${config.server.environment})`, `Transport: ${config.transport.type}${transportInfo}`, `Logging: ${config.logging.level} -> ${config.logging.dir}`, `Sources: ${Object.keys(config.sources).length} configured (${enabledSources} enabled)`, `API Keys: ${apiKeysList}`, '', `Validation: ${validation.valid ? 'PASSED' : 'FAILED'}`, errorsList, ].filter(line => line !== '').join('\n'); return summary; } // Public API for accessing configuration export function getAppConfiguration() { return configManager.get(); } // Public API for environment variable access export function getEnvironmentVariable(name, defaultValue) { return envManager.get(name, defaultValue); } export function setEnvironmentVariable(name, value) { envManager.set(name, value); } export function setEnvironmentIfNotSet(name, value) { envManager.setIfNotSet(name, value); } // Default headers for HTTP requests export const DEFAULT_HEADERS = { 'User-Agent': `${PACKAGE_NAME}/${VERSION} (Security Research - CVE Intelligence Server)`, 'Accept': 'application/json', 'Accept-Language': 'en-US,en;q=0.9', }; //# sourceMappingURL=index.js.map