Skip to main content
Glama

Optimizely DXP MCP Server

by JaxonDigital
security-helper.js11 kB
/** * Security Helper Module * Provides security utilities for protecting sensitive information * Part of Jaxon Digital Optimizely DXP MCP Server */ class SecurityHelper { /** * List of patterns that indicate sensitive information */ static SENSITIVE_PATTERNS = [ /api[-_]?key/i, /api[-_]?secret/i, /client[-_]?key/i, /client[-_]?secret/i, /password/i, /token/i, /bearer/i, /authorization/i, /x-api-key/i ]; /** * Mask a sensitive value for display * @param {string} value - The value to mask * @param {number} showChars - Number of characters to show at start (default 4) * @returns {string} Masked value */ static maskSecret(value, showChars = 4) { if (!value || typeof value !== 'string') { return '***'; } if (value.length <= showChars) { return '***'; } const prefix = value.substring(0, showChars); const maskedLength = Math.min(value.length - showChars, 20); return `${prefix}${'*'.repeat(maskedLength)}`; } /** * Check if a string contains potential secrets * @param {string} text - Text to check * @returns {boolean} True if potential secrets detected */ static containsSecrets(text) { if (!text || typeof text !== 'string') { return false; } // Check for patterns that look like API keys or secrets const suspiciousPatterns = [ /[a-zA-Z0-9]{32,}/, // Long alphanumeric strings /sk_[a-zA-Z0-9]{32,}/, // Stripe-like keys /ey[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+/, // JWT tokens /ghp_[a-zA-Z0-9]{36,}/, // GitHub tokens /[a-f0-9]{40}/, // SHA1 hashes (common for API keys) ]; return suspiciousPatterns.some(pattern => pattern.test(text)); } /** * Sanitize an object by masking sensitive fields * @param {Object} obj - Object to sanitize * @param {Array<string>} additionalFields - Additional field names to mask * @returns {Object} Sanitized copy of the object */ static sanitizeObject(obj, additionalFields = []) { if (!obj || typeof obj !== 'object') { return obj; } const sanitized = JSON.parse(JSON.stringify(obj)); // Deep clone const sensitiveFields = [ 'apiKey', 'apiSecret', 'api_key', 'api_secret', 'clientKey', 'clientSecret', 'client_key', 'client_secret', 'password', 'token', 'secret', 'authorization', ...additionalFields ]; function sanitizeRecursive(obj) { for (const key in obj) { if (obj.hasOwnProperty(key)) { const lowerKey = key.toLowerCase(); // Check if this is a sensitive field if (sensitiveFields.some(field => lowerKey.includes(field.toLowerCase()))) { if (typeof obj[key] === 'string') { obj[key] = SecurityHelper.maskSecret(obj[key]); } } else if (typeof obj[key] === 'object' && obj[key] !== null) { sanitizeRecursive(obj[key]); } } } } sanitizeRecursive(sanitized); return sanitized; } /** * Sanitize error messages to remove sensitive information * @param {string|Error} error - Error message or Error object * @returns {string} Sanitized error message */ static sanitizeError(error) { let message = error instanceof Error ? error.message : String(error); // Remove common patterns that might contain secrets const patterns = [ // API keys in URLs /api[-_]?key[=:]["']?[a-zA-Z0-9-_]+/gi, /client[-_]?key[=:]["']?[a-zA-Z0-9-_]+/gi, // Secrets in error messages /secret[=:]["']?[a-zA-Z0-9-_]+/gi, /password[=:]["']?[a-zA-Z0-9-_]+/gi, // Bearer tokens /bearer\s+[a-zA-Z0-9-_\.]+/gi, // Basic auth /basic\s+[a-zA-Z0-9+\/=]+/gi, // URLs with credentials /https?:\/\/[^:]+:[^@]+@/gi, ]; patterns.forEach(pattern => { message = message.replace(pattern, (match) => { const type = match.split(/[=:]/)[0]; return `${type}=***REDACTED***`; }); }); return message; } /** * Sanitize command strings (for logging) * @param {string} command - Command string that might contain secrets * @returns {string} Sanitized command */ static sanitizeCommand(command) { if (!command || typeof command !== 'string') { return command; } let sanitized = command; // Patterns for PowerShell parameters with secrets const patterns = [ /-ClientKey\s+['"]?([^'";\s]+)['"]?/gi, /-ClientSecret\s+['"]?([^'";\s]+)['"]?/gi, /-ApiKey\s+['"]?([^'";\s]+)['"]?/gi, /-ApiSecret\s+['"]?([^'";\s]+)['"]?/gi, /-Password\s+['"]?([^'";\s]+)['"]?/gi, ]; patterns.forEach(pattern => { sanitized = sanitized.replace(pattern, (match, secret) => { const param = match.split(/\s+/)[0]; return `${param} '${SecurityHelper.maskSecret(secret)}'`; }); }); return sanitized; } /** * Check if a string is a valid UUID * @param {string} str - String to check * @param {boolean} strictV4 - Whether to enforce UUID v4 format * @returns {boolean} True if valid UUID */ static isValidUuid(str, strictV4 = false) { if (!str) return false; const loosePattern = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i; const strictPattern = /^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i; if (strictV4) { return strictPattern.test(str); } return loosePattern.test(str); } /** * Validate that required secrets are present and properly formatted * @param {Object} credentials - Credentials object * @returns {Object} Validation result {valid: boolean, errors: string[]} */ static validateCredentials(credentials) { const errors = []; if (!credentials) { return { valid: false, errors: ['No credentials provided'] }; } // Check API Key if (!credentials.apiKey) { errors.push('API Key is missing'); } else if (credentials.apiKey.length < 20) { errors.push('API Key appears to be invalid (too short)'); } else if (credentials.apiKey.includes(' ')) { errors.push('API Key contains spaces (likely invalid)'); } // Check API Secret if (!credentials.apiSecret) { errors.push('API Secret is missing'); } else if (credentials.apiSecret.length < 20) { errors.push('API Secret appears to be invalid (too short)'); } else if (credentials.apiSecret.includes(' ')) { errors.push('API Secret contains spaces (likely invalid)'); } // Check Project ID - enforce strict UUID v4 format if (!credentials.projectId) { errors.push('Project ID is missing'); } else { // Strict UUID v4 pattern: 8-4-4-4-12 hexadecimal digits const strictUuidPattern = /^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i; const looseUuidPattern = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i; if (!credentials.projectId.match(looseUuidPattern)) { errors.push(`Project ID has invalid format. Expected UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (Got: ${credentials.projectId})`); } else if (!credentials.projectId.match(strictUuidPattern)) { // It's a valid UUID format but not v4 - add warning but don't fail console.warn(`⚠️ Project ID '${credentials.projectId}' is not a standard UUID v4. This may work but could indicate an issue.`); } } return { valid: errors.length === 0, errors }; } /** * Create a safe logging context * @param {Object} context - Context object that might contain secrets * @returns {Object} Safe context for logging */ static createSafeLogContext(context) { const safe = {}; for (const key in context) { if (context.hasOwnProperty(key)) { const lowerKey = key.toLowerCase(); if (lowerKey.includes('secret') || lowerKey.includes('key') || lowerKey.includes('token') || lowerKey.includes('password')) { safe[key] = '***REDACTED***'; } else if (typeof context[key] === 'object') { safe[key] = SecurityHelper.sanitizeObject(context[key]); } else { safe[key] = context[key]; } } } return safe; } /** * Check environment variables for exposed secrets * @returns {Object} Security check result */ static checkEnvironmentSecurity() { const warnings = []; const securityIssues = []; // Check if secrets are in environment const envVars = process.env; const secretVars = [ 'OPTIMIZELY_API_KEY', 'OPTIMIZELY_API_SECRET' ]; secretVars.forEach(varName => { if (envVars[varName]) { // Check if the value looks exposed const value = envVars[varName]; // Check for common mistakes if (value.startsWith('$')) { securityIssues.push(`${varName} appears to be a variable reference, not the actual value`); } else if (value.includes('\\') || value.includes('/')) { warnings.push(`${varName} contains path characters - verify it's not a file path`); } else if (value.length < 20) { warnings.push(`${varName} seems unusually short - verify it's correct`); } } }); return { secure: securityIssues.length === 0, warnings, issues: securityIssues }; } } module.exports = SecurityHelper;

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/JaxonDigital/optimizely-dxp-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server