UNPKG

pms-analysis-reports-mcp-server

Version:

PMS analysis reports server handling maintenance reports, equipment analysis, compliance tracking, and performance metrics with ERP access for data extraction

178 lines 6.98 kB
import { logger } from "../utils/logger.js"; import { ObjectId } from 'mongodb'; export class SanitizationMiddleware { // Sanitize MongoDB query objects to prevent NoSQL injection static sanitizeMongoQuery(query, options = {}) { const { allowedFields, maxStringLength = 1000 } = options; if (query === null || query === undefined) { return query; } if (typeof query === 'string') { return this.sanitizeString(query, maxStringLength); } if (typeof query === 'number') { return this.sanitizeNumber(query); } if (query instanceof Date) { return query; } if (query instanceof ObjectId) { return options.allowObjectIds ? query : query.toString(); } if (Array.isArray(query)) { return query.map(item => this.sanitizeMongoQuery(item, options)); } if (typeof query === 'object') { const sanitized = {}; for (const [key, value] of Object.entries(query)) { // Check for dangerous MongoDB operators if (this.isDangerousOperator(key)) { logger.warn(`Blocked dangerous MongoDB operator: ${key}`); continue; } // Filter allowed fields if specified if (allowedFields && !allowedFields.includes(key)) { logger.warn(`Blocked unauthorized field: ${key}`); continue; } // Recursively sanitize nested objects sanitized[this.sanitizeString(key, 100)] = this.sanitizeMongoQuery(value, options); } return sanitized; } return query; } // Check for dangerous MongoDB operators static isDangerousOperator(key) { const dangerousOperators = [ '$where', // JavaScript execution '$eval', // JavaScript evaluation '$function', // Custom functions '$accumulator', // Custom accumulator functions '$expr', // Can contain $function // Allow common safe operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $and, $or, $not, $exists ]; return dangerousOperators.includes(key); } // Sanitize string inputs static sanitizeString(input, maxLength = 1000) { if (typeof input !== 'string') { return String(input).slice(0, maxLength); } return input .slice(0, maxLength) // Limit length .replace(/[\x00-\x1F\x7F]/g, '') // Remove control characters .replace(/[<>]/g, '') // Remove HTML-like tags .trim(); } // Sanitize numeric inputs static sanitizeNumber(input) { const num = Number(input); if (isNaN(num) || !isFinite(num)) { return null; } // Clamp to safe integer range return Math.max(-Number.MAX_SAFE_INTEGER, Math.min(Number.MAX_SAFE_INTEGER, num)); } // Sanitize search query for Typesense static sanitizeSearchQuery(query) { if (typeof query !== 'string') { return ''; } return query .slice(0, 500) // Limit search query length .replace(/[^\w\s\-_.,;:()]/g, ' ') // Allow only safe characters .replace(/\s+/g, ' ') // Normalize whitespace .trim(); } // Sanitize file paths to prevent directory traversal static sanitizeFilePath(path) { if (typeof path !== 'string') { return ''; } return path .replace(/\.\./g, '') // Remove parent directory references .replace(/[<>:"|?*]/g, '') // Remove invalid filename characters .replace(/^\/+/, '') // Remove leading slashes .slice(0, 255); // Limit path length } // Sanitize user input for logging (remove sensitive data) static sanitizeForLogging(data) { if (data === null || data === undefined) { return data; } if (typeof data === 'string') { // Mask potential sensitive patterns return data .replace(/password[=:]\s*\S+/gi, 'password=***') .replace(/api[_-]?key[=:]\s*\S+/gi, 'api_key=***') .replace(/token[=:]\s*\S+/gi, 'token=***') .replace(/mongodb:\/\/[^@]*@/g, 'mongodb://***@'); } if (Array.isArray(data)) { return data.map(item => this.sanitizeForLogging(item)); } if (typeof data === 'object') { const sanitized = {}; for (const [key, value] of Object.entries(data)) { const lowerKey = key.toLowerCase(); if (lowerKey.includes('password') || lowerKey.includes('secret') || lowerKey.includes('token') || lowerKey.includes('key')) { sanitized[key] = '***'; } else { sanitized[key] = this.sanitizeForLogging(value); } } return sanitized; } return data; } // Sanitize error messages to prevent information leakage static sanitizeErrorMessage(error) { if (!error) { return 'An unknown error occurred'; } let message = ''; if (typeof error === 'string') { message = error; } else if (error.message) { message = error.message; } else { message = 'An error occurred'; } // Remove sensitive information patterns message = message .replace(/mongodb:\/\/[^@]*@[^\/]+/g, 'mongodb://***@***/') // Hide DB credentials .replace(/apikey[=:]\s*\S+/gi, 'apikey=***') // Hide API keys .replace(/password[=:]\s*\S+/gi, 'password=***') // Hide passwords .replace(/\/[a-zA-Z]:[\\\/][^\\\/\s]*/g, '/***/') // Hide file paths .slice(0, 200); // Limit message length return message || 'An error occurred'; } // Comprehensive sanitization for tool responses static sanitizeToolResponse(response) { if (response === null || response === undefined) { return response; } if (typeof response === 'string') { return this.sanitizeString(response, 10000); // Allow longer responses } if (Array.isArray(response)) { return response.map(item => this.sanitizeToolResponse(item)); } if (typeof response === 'object') { const sanitized = {}; for (const [key, value] of Object.entries(response)) { sanitized[key] = this.sanitizeToolResponse(value); } return sanitized; } return response; } } //# sourceMappingURL=sanitization-middleware.js.map