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