UNPKG

survey-mcp-server

Version:

Survey management server handling survey creation, response collection, analysis, and reporting with database access for data management

266 lines 11.6 kB
export { ConfigValidator, configValidator } from './validation.js'; export { configSchema, developmentDefaults, productionDefaults, testDefaults, getEnvironmentDefaults } from './schema.js'; import { configValidator } from './validation.js'; import { configSchema, getEnvironmentDefaults } from './schema.js'; import { config as originalConfig, reloadConfig as originalReloadConfig } from '../utils/config.js'; import { logger } from '../utils/logger.js'; import { ConfigurationError } from '../middleware/error-handling.js'; export class SecureConfigManager { constructor() { this.validatedConfig = null; this.lastValidation = null; this.securityIssuesCount = 0; } static getInstance() { if (!SecureConfigManager.instance) { SecureConfigManager.instance = new SecureConfigManager(); } return SecureConfigManager.instance; } async validateCurrentConfig(options = {}) { const { validateOnLoad = true, strictMode = true, logSecurityIssues = true, throwOnValidationError = true, environment = process.env.NODE_ENV || 'production' } = options; try { // Apply environment-specific defaults const envDefaults = getEnvironmentDefaults(environment); const configWithDefaults = this.mergeConfigWithDefaults(originalConfig, envDefaults); // Validate configuration against schema const validationOptions = { strictMode, allowMissingOptional: true, validateCredentials: true, sanitizeSecrets: true }; const validationResult = configValidator.validateConfiguration(configWithDefaults, configSchema, validationOptions); this.lastValidation = new Date(); this.securityIssuesCount = validationResult.securityIssues?.length || 0; if (logSecurityIssues && validationResult.securityIssues?.length) { const report = configValidator.generateConfigReport(validationResult, true); logger.warn('Configuration security issues detected:'); logger.warn(report); } if (!validationResult.isValid) { const errorReport = configValidator.generateConfigReport(validationResult, false); logger.error('Configuration validation failed:'); logger.error(errorReport); if (throwOnValidationError) { throw new ConfigurationError('Configuration validation failed', { errors: validationResult.errors, securityIssues: validationResult.securityIssues }); } } else { this.validatedConfig = validationResult.sanitizedConfig; logger.info('Configuration validation passed successfully'); if (validationResult.securityIssues?.length === 0) { logger.info('No security issues detected in configuration'); } } } catch (error) { logger.error('Configuration validation error:', error); if (throwOnValidationError) { throw new ConfigurationError(`Configuration validation failed: ${error.message}`, { originalError: error.message }); } } } getValidatedConfig() { if (!this.validatedConfig) { throw new ConfigurationError('Configuration has not been validated. Call validateCurrentConfig() first.'); } return this.validatedConfig; } async reloadAndValidateConfig(options = {}) { // Reload the original configuration originalReloadConfig(); // Validate the new configuration await this.validateCurrentConfig(options); } validateCliArguments(args) { const errors = []; const sanitizedArgs = {}; try { // Validate common CLI arguments const cliValidation = [ { name: 'mongodbUri', pattern: /^mongodb:\/\/.+/, message: 'MongoDB URI must be a valid MongoDB connection string' }, { name: 'mongodbDbName', pattern: /^[a-zA-Z][a-zA-Z0-9_-]*$/, message: 'Database name must contain only alphanumeric characters, hyphens, and underscores' }, { name: 'typesenseHost', pattern: /^[a-zA-Z0-9.-]+$/, message: 'Typesense host must be a valid hostname or IP address' }, { name: 'typesensePort', pattern: /^\d+$/, message: 'Typesense port must be a number' }, { name: 'typesenseProtocol', pattern: /^(http|https)$/, message: 'Typesense protocol must be http or https' }, { name: 'logLevel', pattern: /^(error|warn|info|debug|verbose)$/, message: 'Log level must be one of: error, warn, info, debug, verbose' } ]; for (const [key, value] of Object.entries(args)) { if (value === undefined || value === null) { continue; } // Find validation rule for this argument const validation = cliValidation.find(v => v.name === key); if (validation && typeof value === 'string') { if (!validation.pattern.test(value)) { errors.push(`${key}: ${validation.message}`); continue; } } // Basic sanitization if (typeof value === 'string') { // Remove dangerous characters let sanitized = value.trim(); // Check for path traversal if (sanitized.includes('../') || sanitized.includes('..\\')) { errors.push(`${key}: Path traversal attempts not allowed`); continue; } // Check for script injection if (sanitized.includes('<script') || sanitized.includes('javascript:')) { errors.push(`${key}: Script content not allowed`); continue; } sanitizedArgs[key] = sanitized; } else { sanitizedArgs[key] = value; } } return { isValid: errors.length === 0, errors, sanitizedArgs }; } catch (error) { return { isValid: false, errors: [`CLI argument validation error: ${error.message}`], sanitizedArgs: {} }; } } generateSecurityReport() { const recommendations = []; // Check for common security issues if (!this.lastValidation) { recommendations.push('Configuration has not been validated'); } else { const timeSinceValidation = Date.now() - this.lastValidation.getTime(); if (timeSinceValidation > 24 * 60 * 60 * 1000) { // 24 hours recommendations.push('Configuration validation is outdated (>24 hours)'); } } if (this.securityIssuesCount > 0) { recommendations.push(`${this.securityIssuesCount} security issues detected - review and fix`); } // Check environment const environment = process.env.NODE_ENV || 'unknown'; if (environment === 'unknown') { recommendations.push('NODE_ENV is not set - specify development, production, or test'); } // Check for required security settings in production if (environment === 'production') { const productionChecks = [ { env: 'SECURITY_ENABLE_INPUT_VALIDATION', default: 'true' }, { env: 'SECURITY_ENABLE_SANITIZATION', default: 'true' }, { env: 'PERFORMANCE_ENABLE_CIRCUIT_BREAKER', default: 'true' } ]; for (const check of productionChecks) { if (process.env[check.env] !== 'true') { recommendations.push(`Set ${check.env}=${check.default} for production`); } } } return { validationStatus: !this.lastValidation ? 'not_validated' : this.securityIssuesCount > 0 ? 'failed' : 'passed', lastValidation: this.lastValidation, securityIssuesCount: this.securityIssuesCount, recommendations }; } mergeConfigWithDefaults(config, defaults) { const merged = JSON.parse(JSON.stringify(config)); // Deep clone for (const [path, defaultValue] of Object.entries(defaults)) { const current = this.getNestedValue(merged, path); if (current === undefined || current === null) { this.setNestedValue(merged, path, defaultValue); } } return merged; } getNestedValue(obj, path) { return path.split('.').reduce((current, key) => { return current && current[key] !== undefined ? current[key] : undefined; }, obj); } setNestedValue(obj, path, value) { const keys = path.split('.'); const lastKey = keys.pop(); const target = keys.reduce((current, key) => { if (!current[key] || typeof current[key] !== 'object') { current[key] = {}; } return current[key]; }, obj); target[lastKey] = value; } getConfigFieldInfo(fieldName) { return configSchema.find(field => field.name === fieldName); } listSensitiveFields() { return configSchema .filter(field => field.sensitive) .map(field => field.name); } maskSensitiveValues(config) { const masked = JSON.parse(JSON.stringify(config)); const sensitiveFields = this.listSensitiveFields(); for (const fieldPath of sensitiveFields) { const value = this.getNestedValue(masked, fieldPath); if (typeof value === 'string' && value.length > 0) { if (value.length > 8) { this.setNestedValue(masked, fieldPath, value.substring(0, 4) + '*'.repeat(value.length - 8) + value.substring(value.length - 4)); } else { this.setNestedValue(masked, fieldPath, '*'.repeat(value.length)); } } } return masked; } } export const secureConfigManager = SecureConfigManager.getInstance(); // Initialize configuration validation on module load if in production if (process.env.NODE_ENV === 'production') { secureConfigManager.validateCurrentConfig({ validateOnLoad: true, strictMode: true, logSecurityIssues: true, throwOnValidationError: false // Don't crash on startup in production }).catch(error => { logger.error('Failed to validate configuration on startup:', error); }); } //# sourceMappingURL=index.js.map