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
JavaScript
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