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

288 lines 11.3 kB
/** * Secure error handling utilities * Prevents sensitive information disclosure through error messages */ import { createContextLogger } from './logger.js'; import { getAppConfiguration } from '../config/index.js'; const logger = createContextLogger('ErrorHandler'); /** * Dynamically loads API key environment variable names from configuration */ function getApiKeyEnvVars() { try { const config = getAppConfiguration(); const apiKeyEnvVars = []; // Extract apiKeyEnvVar values from all sources in the configuration if (config.sources) { Object.values(config.sources).forEach((source) => { if (source.apiKeyEnvVar && typeof source.apiKeyEnvVar === 'string') { apiKeyEnvVars.push(source.apiKeyEnvVar); } }); } logger.debug('Loaded API key environment variables from config', { count: apiKeyEnvVars.length, envVars: apiKeyEnvVars, }); return apiKeyEnvVars; } catch (error) { logger.warn('Failed to load API key environment variables from config', { error: error instanceof Error ? error.message : String(error), }); // Fallback to known defaults if config loading fails return ['NVD_API_KEY', 'GITHUB_TOKEN', 'MITRE_API_KEY']; } } // Error types that should be handled differently export var ErrorType; (function (ErrorType) { ErrorType["VALIDATION"] = "validation"; ErrorType["AUTHENTICATION"] = "authentication"; ErrorType["AUTHORIZATION"] = "authorization"; ErrorType["NOT_FOUND"] = "not_found"; ErrorType["RATE_LIMIT"] = "rate_limit"; ErrorType["INTERNAL"] = "internal"; ErrorType["EXTERNAL_API"] = "external_api"; ErrorType["NETWORK"] = "network"; ErrorType["TIMEOUT"] = "timeout"; ErrorType["CONFIGURATION"] = "configuration"; })(ErrorType || (ErrorType = {})); export class SecureErrorHandler { static instance; isDevelopment; constructor() { this.isDevelopment = process.env.NODE_ENV === 'development'; } static getInstance() { if (!SecureErrorHandler.instance) { SecureErrorHandler.instance = new SecureErrorHandler(); } return SecureErrorHandler.instance; } /** * Creates a safe error response for clients */ createSafeError(errorDetails, requestId) { const { type, originalError, userMessage, statusCode } = errorDetails; // Log the full error details internally this.logError(errorDetails, requestId); // Create safe user-facing message const safeMessage = this.createSafeMessage(type, originalError, userMessage); return { error: this.getErrorTitle(type), message: safeMessage, code: this.getErrorCode(type, statusCode), timestamp: new Date().toISOString(), requestId, }; } /** * Sanitizes error messages to remove sensitive information */ sanitizeErrorMessage(message) { let sanitized = message; // Get API key environment variables from configuration const apiKeyEnvVars = getApiKeyEnvVars(); // Only redact actual API key values from environment variables apiKeyEnvVars.forEach(envVar => { const apiKeyValue = process.env[envVar]; if (apiKeyValue && apiKeyValue.length > 8) { // Escape the API key value for regex const escapedValue = apiKeyValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // Replace the exact API key value anywhere it appears sanitized = sanitized.replace(new RegExp(escapedValue, 'g'), '[API_KEY_REDACTED]'); // Also handle quoted versions sanitized = sanitized.replace(new RegExp(`["']${escapedValue}["']`, 'g'), '"[API_KEY_REDACTED]"'); // Handle assignment patterns with the actual value sanitized = sanitized.replace(new RegExp(`${envVar.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*[=:]\\s*["']?${escapedValue}["']?`, 'gi'), `${envVar}=[API_KEY_REDACTED]`); } }); // Remove null bytes (security issue) sanitized = sanitized.replace(/\0/g, ''); // Clean up multiple spaces sanitized = sanitized.replace(/\s+/g, ' ').trim(); // Return original message if nothing was redacted and it's safe return sanitized || 'An error occurred while processing your request'; } /** * Determines if an error contains sensitive information */ containsSensitiveInfo(error) { const message = error.message || ''; const stack = error.stack || ''; const fullText = `${message} ${stack}`; // Check if any actual API key values appear in the error const apiKeyEnvVars = getApiKeyEnvVars(); for (const envVar of apiKeyEnvVars) { const apiKeyValue = process.env[envVar]; if (apiKeyValue && apiKeyValue.length > 8 && fullText.includes(apiKeyValue)) { return true; } } return false; } /** * Creates standardized error messages based on error type */ createSafeMessage(type, originalError, userMessage) { // If a user message is provided, use it (but still sanitize) if (userMessage) { return this.sanitizeErrorMessage(userMessage); } // Default safe messages by type switch (type) { case ErrorType.VALIDATION: return this.sanitizeErrorMessage(originalError.message); case ErrorType.AUTHENTICATION: return 'Authentication failed. Please check your credentials.'; case ErrorType.AUTHORIZATION: return 'You do not have permission to access this resource.'; case ErrorType.NOT_FOUND: return 'The requested resource was not found.'; case ErrorType.RATE_LIMIT: return 'Too many requests. Please try again later.'; case ErrorType.EXTERNAL_API: return 'External service is temporarily unavailable. Please try again later.'; case ErrorType.NETWORK: return 'Network error occurred. Please check your connection and try again.'; case ErrorType.TIMEOUT: return 'Request timed out. Please try again.'; case ErrorType.CONFIGURATION: return 'Service configuration error. Please contact support.'; case ErrorType.INTERNAL: default: // For internal errors, never expose the original message if (this.isDevelopment) { return `Development Error: ${this.sanitizeErrorMessage(originalError.message)}`; } return 'An internal server error occurred. Please try again later.'; } } getErrorTitle(type) { switch (type) { case ErrorType.VALIDATION: return 'Validation Error'; case ErrorType.AUTHENTICATION: return 'Authentication Error'; case ErrorType.AUTHORIZATION: return 'Authorization Error'; case ErrorType.NOT_FOUND: return 'Not Found'; case ErrorType.RATE_LIMIT: return 'Rate Limit Exceeded'; case ErrorType.EXTERNAL_API: return 'External Service Error'; case ErrorType.NETWORK: return 'Network Error'; case ErrorType.TIMEOUT: return 'Timeout Error'; case ErrorType.CONFIGURATION: return 'Configuration Error'; case ErrorType.INTERNAL: default: return 'Internal Server Error'; } } getErrorCode(type, statusCode) { const typeCode = type.toUpperCase(); if (statusCode) { return `${typeCode}_${statusCode}`; } return typeCode; } logError(errorDetails, requestId) { const { type, originalError, context, sensitive } = errorDetails; const logData = { type, message: originalError.message, stack: originalError.stack, context, requestId, sensitive: sensitive || this.containsSensitiveInfo(originalError), timestamp: new Date().toISOString(), }; // Log with appropriate level based on error type switch (type) { case ErrorType.VALIDATION: case ErrorType.AUTHENTICATION: case ErrorType.AUTHORIZATION: case ErrorType.NOT_FOUND: case ErrorType.RATE_LIMIT: logger.warn('Client error occurred', logData); break; case ErrorType.EXTERNAL_API: case ErrorType.NETWORK: case ErrorType.TIMEOUT: logger.warn('External service error', logData); break; case ErrorType.CONFIGURATION: case ErrorType.INTERNAL: default: logger.error('Internal server error', originalError, logData); break; } } /** * Helper method to create validation errors */ static createValidationError(message, context) { return { type: ErrorType.VALIDATION, originalError: new Error(message), context, statusCode: 400, }; } /** * Helper method to create not found errors */ static createNotFoundError(resource, context) { return { type: ErrorType.NOT_FOUND, originalError: new Error(`${resource} not found`), context, statusCode: 404, userMessage: 'The requested resource was not found.', }; } /** * Helper method to create internal errors */ static createInternalError(originalError, context) { return { type: ErrorType.INTERNAL, originalError, context, statusCode: 500, sensitive: true, }; } /** * Helper method to create external API errors */ static createExternalApiError(service, originalError, context) { return { type: ErrorType.EXTERNAL_API, originalError, context: { ...context, service }, statusCode: 503, userMessage: `External service (${service}) is temporarily unavailable.`, }; } /** * Helper method to create rate limit errors */ static createRateLimitError(limit, window, context) { return { type: ErrorType.RATE_LIMIT, originalError: new Error(`Rate limit exceeded: ${limit} requests per ${window}`), context: { ...context, limit, window }, statusCode: 429, userMessage: 'Too many requests. Please try again later.', }; } } // Export singleton instance export const secureErrorHandler = SecureErrorHandler.getInstance(); //# sourceMappingURL=secure-error-handler.js.map