mcp-sanitizer
Version:
Comprehensive security sanitization library for Model Context Protocol (MCP) servers with trusted security libraries
310 lines (267 loc) • 9.57 kB
JavaScript
/**
* 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')
/**
* 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'
}
/**
* 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 }
]
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)
}
// 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.')
}
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,
strictMode = false,
customPatterns = []
} = config
return {
detect: (input, options = {}) => {
const mergedOptions = { ...options, strictMode, customPatterns }
if (!enableCommandInjection &&
!enableSQLInjection &&
!enablePrototypePollution &&
!enableTemplateInjection) {
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
}
return results
},
isSecure: (input, options = {}) => {
return !this.detect(input, options).detected
}
}
}
// Export individual modules
module.exports = {
// Individual detection modules
commandInjection,
sqlInjection,
prototypePollution,
templateInjection,
// Combined detection functions
detectAllPatterns,
hasSecurityPatterns,
analyzeSecurityPatterns,
createPatternDetector,
// Utility functions
getHigherSeverity,
getRiskLevel,
// Constants
SEVERITY_LEVELS,
PATTERN_TYPES
}