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
JavaScript
/**
* 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