UNPKG

defect-inspection-tools-mcp-server

Version:

Defect inspection tools server handling defect detection, analysis, and reporting with AI/ML capabilities for quality control

302 lines 10.8 kB
import { logger } from '../utils/logger.js'; import { ObjectId } from 'mongodb'; export class SanitizationMiddleware { // Main sanitization method for tool arguments static sanitizeToolArguments(args, options = {}) { try { return this.sanitizeValue(args, { maxStringLength: options.maxStringLength || 1000, allowHtml: options.allowHtml || false, allowObjectIds: options.allowObjectIds || true, ...options }); } catch (error) { logger.error('Error sanitizing tool arguments:', error); return {}; } } // MongoDB query sanitization - prevents NoSQL injection static sanitizeMongoQuery(query, options = {}) { try { if (!query || typeof query !== 'object') { return {}; } const sanitized = {}; for (const [key, value] of Object.entries(query)) { // Skip dangerous operators if (this.DANGEROUS_MONGO_OPERATORS.includes(key)) { logger.warn(`Blocked dangerous MongoDB operator: ${key}`); continue; } // Sanitize field names const sanitizedKey = this.sanitizeString(key, 100); if (!sanitizedKey) continue; // Check allowed fields if (options.allowedFields && !options.allowedFields.includes(sanitizedKey)) { logger.warn(`Blocked non-allowed field: ${sanitizedKey}`); continue; } // Recursively sanitize values sanitized[sanitizedKey] = this.sanitizeMongoValue(value, options); } return sanitized; } catch (error) { logger.error('Error sanitizing MongoDB query:', error); return {}; } } // Typesense search query sanitization static sanitizeSearchQuery(query, maxLength = 500) { try { if (!query || typeof query !== 'string') { return ''; } let sanitized = query.trim(); // Limit length if (sanitized.length > maxLength) { sanitized = sanitized.substring(0, maxLength); } // Remove dangerous patterns for (const pattern of this.DANGEROUS_TYPESENSE_CHARS) { sanitized = sanitized.replace(new RegExp(pattern, 'gi'), ''); } // Allow only safe characters for search sanitized = sanitized.replace(/[^\w\s\-_.,;:()\[\]"']/g, ' '); // Normalize whitespace sanitized = sanitized.replace(/\s+/g, ' ').trim(); return sanitized; } catch (error) { logger.error('Error sanitizing search query:', error); return ''; } } // Error message sanitization - prevents information leakage static sanitizeErrorMessage(error, maxLength = 200) { try { let message = ''; if (error instanceof Error) { message = error.message; } else if (typeof error === 'string') { message = error; } else if (error && typeof error === 'object' && error.message) { message = error.message; } else { message = 'An error occurred'; } // Remove sensitive information message = message .replace(/mongodb:\/\/[^@]+@[^\/]+/g, 'mongodb://***:***@***/') // MongoDB credentials .replace(/password[=:]\s*[^\s]+/gi, 'password=***') // Password fields .replace(/api[_-]?key[=:]\s*[^\s]+/gi, 'api_key=***') // API keys .replace(/token[=:]\s*[^\s]+/gi, 'token=***') // Tokens .replace(/\/[a-zA-Z]:\/[^\/]+/g, '/***') // File paths .replace(/\/home\/[^\/]+/g, '/home/***') // Home directories .replace(/\/Users\/[^\/]+/g, '/Users/***') // User directories .replace(/ObjectId\("[^"]+"\)/g, 'ObjectId("***")'); // ObjectIds // Limit length if (message.length > maxLength) { message = message.substring(0, maxLength - 3) + '...'; } return message; } catch (sanitizeError) { logger.error('Error sanitizing error message:', sanitizeError); return 'An error occurred'; } } // Tool response sanitization - ensures safe output static sanitizeToolResponse(response, maxLength = 10000) { try { return this.sanitizeValue(response, { maxStringLength: maxLength, allowHtml: false, allowObjectIds: true }); } catch (error) { logger.error('Error sanitizing tool response:', error); return { error: 'Response sanitization failed' }; } } // Logging sanitization - masks sensitive data in logs static sanitizeForLogging(data) { try { return this.sanitizeValue(data, { maxStringLength: 500, allowHtml: false, allowObjectIds: false }); } catch (error) { logger.error('Error sanitizing for logging:', error); return { error: 'Log sanitization failed' }; } } // File path sanitization - prevents directory traversal static sanitizeFilePath(path) { try { if (!path || typeof path !== 'string') { return ''; } // Remove dangerous patterns const sanitized = path .replace(/\.\./g, '') // Remove parent directory references .replace(/[<>:"|?*]/g, '') // Remove invalid filename characters .replace(/^\/+/, '') // Remove leading slashes .replace(/\/+/g, '/') // Normalize slashes .trim(); // Limit length return sanitized.length > 255 ? sanitized.substring(0, 255) : sanitized; } catch (error) { logger.error('Error sanitizing file path:', error); return ''; } } // Private helper methods static sanitizeValue(value, options) { if (value === null || value === undefined) { return value; } if (typeof value === 'string') { return this.sanitizeString(value, options.maxStringLength, options.allowHtml); } if (typeof value === 'number') { return this.sanitizeNumber(value); } if (typeof value === 'boolean') { return value; } if (value instanceof Date) { return value; } if (value instanceof ObjectId) { return options.allowObjectIds ? value : value.toString(); } if (Array.isArray(value)) { return value.map(item => this.sanitizeValue(item, options)); } if (typeof value === 'object') { const sanitized = {}; for (const [key, val] of Object.entries(value)) { const sanitizedKey = this.sanitizeString(key, 100); if (sanitizedKey) { sanitized[sanitizedKey] = this.sanitizeValue(val, options); } } return sanitized; } return value; } static sanitizeString(str, maxLength = 1000, allowHtml = false) { if (typeof str !== 'string') { return ''; } let sanitized = str; // Remove control characters sanitized = sanitized.replace(/[\x00-\x1F\x7F]/g, ''); // Remove HTML if not allowed if (!allowHtml) { sanitized = sanitized.replace(/<[^>]*>/g, ''); } // Remove dangerous patterns for (const pattern of this.DANGEROUS_TYPESENSE_CHARS) { sanitized = sanitized.replace(new RegExp(pattern, 'gi'), ''); } // Limit length if (sanitized.length > maxLength) { sanitized = sanitized.substring(0, maxLength); } return sanitized.trim(); } static sanitizeNumber(num) { if (typeof num !== 'number' || isNaN(num)) { return 0; } // Clamp to safe range const min = -Number.MAX_SAFE_INTEGER; const max = Number.MAX_SAFE_INTEGER; return Math.max(min, Math.min(max, num)); } static sanitizeMongoValue(value, options) { if (value === null || value === undefined) { return value; } if (typeof value === 'string') { return this.sanitizeString(value, options.maxStringLength || 1000); } if (typeof value === 'number') { return this.sanitizeNumber(value); } if (typeof value === 'boolean') { return value; } if (value instanceof Date) { return value; } if (value instanceof ObjectId) { return value; } if (Array.isArray(value)) { return value.map(item => this.sanitizeMongoValue(item, options)); } if (typeof value === 'object') { // Check for dangerous operators in nested objects for (const key of Object.keys(value)) { if (this.DANGEROUS_MONGO_OPERATORS.includes(key)) { logger.warn(`Blocked dangerous MongoDB operator in nested object: ${key}`); return null; } } const sanitized = {}; for (const [key, val] of Object.entries(value)) { const sanitizedKey = this.sanitizeString(key, 100); if (sanitizedKey) { sanitized[sanitizedKey] = this.sanitizeMongoValue(val, options); } } return sanitized; } return value; } } SanitizationMiddleware.DANGEROUS_MONGO_OPERATORS = [ '$where', '$eval', '$function', '$accumulator', '$expr', '$javascript', '$regex', '$near', '$nearSphere', '$geoIntersects', '$geoWithin', '$centerSphere', '$center', '$box', '$polygon', '$geometry' ]; SanitizationMiddleware.DANGEROUS_TYPESENSE_CHARS = [ '<script', 'javascript:', 'eval(', 'function(', 'setTimeout(', 'setInterval(', 'document.', 'window.', 'location.', 'alert(', 'confirm(', 'prompt(' ]; //# sourceMappingURL=sanitization-middleware.js.map