UNPKG

mcp-sanitizer

Version:

Comprehensive security sanitization library for Model Context Protocol (MCP) servers with trusted security libraries

332 lines (287 loc) 10.6 kB
/** * MCP Sanitizer Pattern Detection Modules * * Exports all pattern detection modules for the MCP Sanitizer. * This provides a centralized access point for all security pattern * detection functionality. * * Each module includes: * - Detection functions that return detailed results * - Simple boolean check functions * - Individual pattern checkers for granular control * - Exportable regex patterns for reuse * - Severity level constants * * Based on security best practices from HTML/XML sanitizers like DOMPurify, * OWASP guidelines, and comprehensive security research. */ const commandInjection = require('./command-injection'); const sqlInjection = require('./sql-injection'); const prototypePollution = require('./prototype-pollution'); const templateInjection = require('./template-injection'); const nosqlInjection = require('./nosql-injection'); /** * Combined severity levels from all modules */ const SEVERITY_LEVELS = { CRITICAL: 'critical', HIGH: 'high', MEDIUM: 'medium', LOW: 'low' }; /** * Pattern type constants for easier identification */ const PATTERN_TYPES = { COMMAND_INJECTION: 'command_injection', SQL_INJECTION: 'sql_injection', PROTOTYPE_POLLUTION: 'prototype_pollution', TEMPLATE_INJECTION: 'template_injection', NOSQL_INJECTION: 'nosql_injection' }; /** * Comprehensive security pattern detection * Runs all pattern detection modules and combines results * * @param {string|Object} input - The input to analyze * @param {Object} options - Detection options * @returns {Object} Combined detection result */ function detectAllPatterns (input, options = {}) { const results = { detected: false, severity: null, patterns: [], detectionResults: {}, summary: { totalPatterns: 0, patternsByType: {}, patternsBySeverity: {} } }; // Run all detection modules const detectionModules = [ { name: PATTERN_TYPES.COMMAND_INJECTION, module: commandInjection }, { name: PATTERN_TYPES.SQL_INJECTION, module: sqlInjection }, { name: PATTERN_TYPES.PROTOTYPE_POLLUTION, module: prototypePollution }, { name: PATTERN_TYPES.TEMPLATE_INJECTION, module: templateInjection }, { name: PATTERN_TYPES.NOSQL_INJECTION, module: nosqlInjection } ]; for (const { name, module } of detectionModules) { let detectResult; // Call the appropriate detection function based on module if (name === PATTERN_TYPES.COMMAND_INJECTION) { detectResult = module.detectCommandInjection(input, options); } else if (name === PATTERN_TYPES.SQL_INJECTION) { detectResult = module.detectSQLInjection(input, options); } else if (name === PATTERN_TYPES.PROTOTYPE_POLLUTION) { detectResult = module.detectPrototypePollution(input, options); } else if (name === PATTERN_TYPES.TEMPLATE_INJECTION) { detectResult = module.detectTemplateInjection(input, options); } else if (name === PATTERN_TYPES.NOSQL_INJECTION) { detectResult = module.detectNoSQLInjection(input, options); } // Store individual results results.detectionResults[name] = detectResult; // Combine patterns if (detectResult.detected) { results.detected = true; results.patterns.push(...detectResult.patterns.map(p => `${name}:${p}`)); // Update severity to highest found results.severity = getHigherSeverity(results.severity, detectResult.severity); // Update summary results.summary.patternsByType[name] = detectResult.patterns.length; if (detectResult.severity) { results.summary.patternsBySeverity[detectResult.severity] = (results.summary.patternsBySeverity[detectResult.severity] || 0) + detectResult.patterns.length; } } else { results.summary.patternsByType[name] = 0; } } results.summary.totalPatterns = results.patterns.length; // Generate combined message if (results.detected) { const typeCount = Object.values(results.summary.patternsByType) .filter(count => count > 0).length; results.message = `${results.summary.totalPatterns} security patterns detected across ${typeCount} categories (severity: ${results.severity})`; } return results; } /** * Quick boolean check for any security patterns * @param {string|Object} input - The input to check * @returns {boolean} True if any security patterns are detected */ function hasSecurityPatterns (input) { return detectAllPatterns(input).detected; } /** * Get detailed security analysis with recommendations * @param {string|Object} input - The input to analyze * @param {Object} options - Analysis options * @returns {Object} Detailed security analysis */ function analyzeSecurityPatterns (input, options = {}) { const detection = detectAllPatterns(input, options); const analysis = { ...detection, recommendations: [], riskLevel: getRiskLevel(detection.severity), shouldBlock: detection.severity === SEVERITY_LEVELS.CRITICAL }; // Generate recommendations based on detected patterns if (detection.detectionResults[PATTERN_TYPES.COMMAND_INJECTION]?.detected) { analysis.recommendations.push('Input contains command injection patterns. Sanitize shell metacharacters and validate against allowed commands.'); } if (detection.detectionResults[PATTERN_TYPES.SQL_INJECTION]?.detected) { analysis.recommendations.push('Input contains SQL injection patterns. Use parameterized queries and validate SQL keywords.'); } if (detection.detectionResults[PATTERN_TYPES.PROTOTYPE_POLLUTION]?.detected) { analysis.recommendations.push('Input contains prototype pollution patterns. Validate object keys and use Object.create(null) for safe objects.'); } if (detection.detectionResults[PATTERN_TYPES.TEMPLATE_INJECTION]?.detected) { analysis.recommendations.push('Input contains template injection patterns. Sanitize template syntax and use safe template engines.'); } if (detection.detectionResults[PATTERN_TYPES.NOSQL_INJECTION]?.detected) { analysis.recommendations.push('Input contains NoSQL injection patterns. Validate database operators, sanitize user input, and use parameterized queries.'); } return analysis; } /** * Get risk level based on severity */ function getRiskLevel (severity) { switch (severity) { case SEVERITY_LEVELS.CRITICAL: return 'CRITICAL'; case SEVERITY_LEVELS.HIGH: return 'HIGH'; case SEVERITY_LEVELS.MEDIUM: return 'MEDIUM'; case SEVERITY_LEVELS.LOW: return 'LOW'; default: return 'NONE'; } } /** * Get the higher severity between two severity levels */ function getHigherSeverity (current, newSeverity) { if (!current) return newSeverity; if (!newSeverity) return current; const severityOrder = [ SEVERITY_LEVELS.LOW, SEVERITY_LEVELS.MEDIUM, SEVERITY_LEVELS.HIGH, SEVERITY_LEVELS.CRITICAL ]; const currentIndex = severityOrder.indexOf(current); const newIndex = severityOrder.indexOf(newSeverity); return newIndex > currentIndex ? newSeverity : current; } /** * Create a pattern detector configured for specific use cases * @param {Object} config - Configuration options * @returns {Object} Configured detector functions */ function createPatternDetector (config = {}) { const { enableCommandInjection = true, enableSQLInjection = true, enablePrototypePollution = true, enableTemplateInjection = true, enableNoSQLInjection = true, strictMode = false, customPatterns = [] } = config; return { detect: (input, options = {}) => { const mergedOptions = { ...options, strictMode, customPatterns }; if (!enableCommandInjection && !enableSQLInjection && !enablePrototypePollution && !enableTemplateInjection && !enableNoSQLInjection) { return { detected: false, severity: null, patterns: [] }; } // Run only enabled detectors const results = { detected: false, severity: null, patterns: [], detectionResults: {} }; if (enableCommandInjection) { const result = commandInjection.detectCommandInjection(input, mergedOptions); if (result.detected) { results.detected = true; results.patterns.push(...result.patterns); results.severity = getHigherSeverity(results.severity, result.severity); } results.detectionResults.commandInjection = result; } if (enableSQLInjection) { const result = sqlInjection.detectSQLInjection(input, mergedOptions); if (result.detected) { results.detected = true; results.patterns.push(...result.patterns); results.severity = getHigherSeverity(results.severity, result.severity); } results.detectionResults.sqlInjection = result; } if (enablePrototypePollution) { const result = prototypePollution.detectPrototypePollution(input, mergedOptions); if (result.detected) { results.detected = true; results.patterns.push(...result.patterns); results.severity = getHigherSeverity(results.severity, result.severity); } results.detectionResults.prototypePollution = result; } if (enableTemplateInjection) { const result = templateInjection.detectTemplateInjection(input, mergedOptions); if (result.detected) { results.detected = true; results.patterns.push(...result.patterns); results.severity = getHigherSeverity(results.severity, result.severity); } results.detectionResults.templateInjection = result; } if (enableNoSQLInjection) { const result = nosqlInjection.detectNoSQLInjection(input, mergedOptions); if (result.detected) { results.detected = true; results.patterns.push(...result.patterns); results.severity = getHigherSeverity(results.severity, result.severity); } results.detectionResults.nosqlInjection = result; } return results; }, isSecure: (input, options = {}) => { return !this.detect(input, options).detected; } }; } // Export individual modules module.exports = { // Individual detection modules commandInjection, sqlInjection, prototypePollution, templateInjection, nosqlInjection, // Combined detection functions detectAllPatterns, hasSecurityPatterns, analyzeSecurityPatterns, createPatternDetector, // Utility functions getHigherSeverity, getRiskLevel, // Constants SEVERITY_LEVELS, PATTERN_TYPES };