UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

408 lines (407 loc) 16 kB
import logger from '../../../logger.js'; export class DataSanitizer { static instance = null; config; violations = []; XSS_PATTERNS = [ /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, /<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, /javascript:/gi, /vbscript:/gi, /onload\s*=/gi, /onerror\s*=/gi, /onclick\s*=/gi, /onmouseover\s*=/gi, /<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi, /<embed\b[^>]*>/gi, /<link\b[^>]*>/gi, /<meta\b[^>]*>/gi ]; COMMAND_INJECTION_PATTERNS = [ /[;&|`${}[\]]/g, /\.\.\//g, /~\//g, /\/etc\//g, /\/proc\//g, /\/sys\//g, /\/dev\//g, /\|\s*\w+/g, /&&\s*\w+/g, /;\s*\w+/g, /`[^`]*`/g, /\$\([^)]*\)/g ]; DEVELOPMENT_WHITELIST = [ 'e.g.', 'i.e.', 'etc.', 'API', 'UI', 'UX', 'DB', 'SQL', 'HTTP', 'HTTPS', 'JSON', 'XML', 'CSS', 'HTML', 'JS', 'TS', 'React', 'Vue', 'Angular', 'Node.js', 'Express', 'MongoDB', 'PostgreSQL', 'MySQL', 'Redis', 'Docker', 'Kubernetes', 'AWS', 'Azure', 'GCP', 'CI/CD', 'REST', 'GraphQL' ]; SQL_INJECTION_PATTERNS = [ /(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION|SCRIPT)\b)/gi, /('|(\\')|(;)|(--)|(\s)|(\/\*)|(\*\/))/gi, /(\b(OR|AND)\b.*?[=<>])/gi, /(\b(LIKE)\b.*?['"])/gi, /(INFORMATION_SCHEMA|SYSOBJECTS|SYSCOLUMNS)/gi ]; ENCODING_PATTERNS = [ /%[0-9a-fA-F]{2}/g, /&#x?[0-9a-fA-F]+;/g, /\\u[0-9a-fA-F]{4}/g, /\\x[0-9a-fA-F]{2}/g ]; constructor(config) { this.config = { enableXssProtection: true, enableCommandInjectionProtection: true, enableSqlInjectionProtection: true, maxStringLength: 10000, maxArrayLength: 1000, maxObjectDepth: 10, allowedHtmlTags: ['b', 'i', 'em', 'strong', 'p', 'br', 'ul', 'ol', 'li'], allowedProtocols: ['http:', 'https:', 'mailto:'], strictMode: true, logViolations: true, ...config }; logger.info({ config: this.config }, 'Data Sanitizer initialized'); } static getInstance(config) { if (!DataSanitizer.instance) { DataSanitizer.instance = new DataSanitizer(config); } return DataSanitizer.instance; } async sanitizeTask(task) { const startTime = Date.now(); const violations = []; try { const sanitizedTask = { ...task, title: this.sanitizeString(task.title, 'title', violations), description: this.sanitizeString(task.description, 'description', violations), acceptanceCriteria: task.acceptanceCriteria.map((criteria, index) => this.sanitizeString(criteria, `acceptanceCriteria[${index}]`, violations)), filePaths: task.filePaths.map((filePath, index) => this.sanitizeFilePath(filePath, `filePaths[${index}]`, violations)), dependencies: this.sanitizeArray(task.dependencies, 'dependencies', violations), validationMethods: { automated: task.validationMethods.automated.map((method, index) => this.sanitizeString(method, `validationMethods.automated[${index}]`, violations)), manual: task.validationMethods.manual.map((method, index) => this.sanitizeString(method, `validationMethods.manual[${index}]`, violations)) }, metadata: this.sanitizeObject(task.metadata, 'metadata', violations, 0) }; const sanitizationTime = Date.now() - startTime; if (violations.length > 0 && this.config.logViolations) { logger.warn({ taskId: task.id, violations: violations.length, violationTypes: violations.map(v => v.violationType) }, 'Task sanitization violations detected'); } return { success: violations.filter(v => v.severity === 'critical').length === 0, sanitizedData: sanitizedTask, originalData: task, violations, sanitizationTime }; } catch (error) { logger.error({ err: error, taskId: task.id }, 'Task sanitization failed'); return { success: false, originalData: task, violations: [{ field: 'task', violationType: 'malformed', originalValue: task, severity: 'critical', description: `Sanitization error: ${error instanceof Error ? error.message : String(error)}` }], sanitizationTime: Date.now() - startTime }; } } isSystemIdentifier(fieldName) { const systemIdFields = [ 'id', 'taskId', 'epicId', 'projectId', 'dependencyId', 'createdBy', 'updatedBy', 'assignedAgent' ]; return systemIdFields.includes(fieldName) || systemIdFields.some(field => fieldName.endsWith(field)) || fieldName.includes('.id') || fieldName.includes('Id'); } sanitizeString(input, fieldName, violations) { if (!input || typeof input !== 'string') { return input; } if (this.isSystemIdentifier(fieldName)) { return input; } let sanitized = input; if (sanitized.length > this.config.maxStringLength) { violations.push({ field: fieldName, violationType: 'length', originalValue: input, sanitizedValue: sanitized.substring(0, this.config.maxStringLength), severity: 'medium', description: `String exceeds maximum length of ${this.config.maxStringLength}` }); sanitized = sanitized.substring(0, this.config.maxStringLength); } if (this.config.enableXssProtection && !this.isWhitelistedContent(sanitized)) { const originalSanitized = sanitized; sanitized = this.removeXssPatterns(sanitized); if (sanitized !== originalSanitized) { violations.push({ field: fieldName, violationType: 'xss', originalValue: originalSanitized, sanitizedValue: sanitized, severity: 'high', description: 'Potential XSS patterns removed' }); } } if (this.config.enableCommandInjectionProtection && !this.isWhitelistedContent(sanitized)) { const originalSanitized = sanitized; sanitized = this.removeCommandInjectionPatterns(sanitized); if (sanitized !== originalSanitized) { violations.push({ field: fieldName, violationType: 'injection', originalValue: originalSanitized, sanitizedValue: sanitized, severity: 'critical', description: 'Potential command injection patterns removed' }); } } if (this.config.enableSqlInjectionProtection && !this.isWhitelistedContent(sanitized)) { const originalSanitized = sanitized; sanitized = this.removeSqlInjectionPatterns(sanitized); if (sanitized !== originalSanitized) { violations.push({ field: fieldName, violationType: 'injection', originalValue: originalSanitized, sanitizedValue: sanitized, severity: 'high', description: 'Potential SQL injection patterns removed' }); } } if (!this.isWhitelistedContent(sanitized)) { const encodingViolations = this.detectEncodingAttacks(sanitized); if (encodingViolations.length > 0) { violations.push({ field: fieldName, violationType: 'encoding', originalValue: input, sanitizedValue: sanitized, severity: 'medium', description: 'Suspicious encoding patterns detected' }); } } return sanitized; } isWhitelistedContent(content) { const lowerContent = content.toLowerCase(); return this.DEVELOPMENT_WHITELIST.some(term => lowerContent.includes(term.toLowerCase())); } sanitizeFilePath(filePath, fieldName, violations) { if (!filePath || typeof filePath !== 'string') { return filePath; } let sanitized = filePath; const dangerousPatterns = [ /\.\.\//g, /~\//g, /\/etc\//g, /\/proc\//g, /\/sys\//g, /\/dev\//g, /\0/g ]; for (const pattern of dangerousPatterns) { if (pattern.test(sanitized)) { violations.push({ field: fieldName, violationType: 'injection', originalValue: filePath, sanitizedValue: sanitized.replace(pattern, ''), severity: 'critical', description: 'Dangerous path pattern detected' }); sanitized = sanitized.replace(pattern, ''); } } return sanitized; } sanitizeArray(array, fieldName, violations) { if (!Array.isArray(array)) { return array; } if (array.length > this.config.maxArrayLength) { violations.push({ field: fieldName, violationType: 'length', originalValue: array, sanitizedValue: array.slice(0, this.config.maxArrayLength), severity: 'medium', description: `Array exceeds maximum length of ${this.config.maxArrayLength}` }); return array.slice(0, this.config.maxArrayLength); } return array; } sanitizeObject(obj, fieldName, violations, depth) { if (depth > this.config.maxObjectDepth) { violations.push({ field: fieldName, violationType: 'pattern', originalValue: obj, severity: 'medium', description: `Object depth exceeds maximum of ${this.config.maxObjectDepth}` }); return {}; } if (obj === null || typeof obj !== 'object') { return obj; } if (Array.isArray(obj)) { return this.sanitizeArray(obj, fieldName, violations); } const sanitized = {}; for (const [key, value] of Object.entries(obj)) { const sanitizedKey = key; if (typeof value === 'string') { sanitized[sanitizedKey] = this.sanitizeString(value, `${fieldName}.${key}`, violations); } else if (typeof value === 'object') { sanitized[sanitizedKey] = this.sanitizeObject(value, `${fieldName}.${key}`, violations, depth + 1); } else { sanitized[sanitizedKey] = value; } } return sanitized; } removeXssPatterns(input) { let sanitized = input; for (const pattern of this.XSS_PATTERNS) { sanitized = sanitized.replace(pattern, ''); } sanitized = sanitized.replace(/\s*on\w+\s*=\s*["'][^"']*["']/gi, ''); sanitized = sanitized.replace(/href\s*=\s*["']([^"']*)["']/gi, (match, url) => { const protocol = url.split(':')[0].toLowerCase() + ':'; if (this.config.allowedProtocols.includes(protocol)) { return match; } return ''; }); return sanitized; } removeCommandInjectionPatterns(input) { let sanitized = input; for (const pattern of this.COMMAND_INJECTION_PATTERNS) { sanitized = sanitized.replace(pattern, ''); } return sanitized; } removeSqlInjectionPatterns(input) { let sanitized = input; for (const pattern of this.SQL_INJECTION_PATTERNS) { sanitized = sanitized.replace(pattern, ''); } return sanitized; } detectEncodingAttacks(input) { const violations = []; for (const pattern of this.ENCODING_PATTERNS) { if (pattern.test(input)) { violations.push(pattern.source); } } return violations; } async sanitizeInput(input, fieldName = 'input') { const startTime = Date.now(); const violations = []; try { let sanitized; if (typeof input === 'string') { sanitized = this.sanitizeString(input, fieldName, violations); } else if (Array.isArray(input)) { sanitized = this.sanitizeArray(input, fieldName, violations); } else if (typeof input === 'object' && input !== null) { sanitized = this.sanitizeObject(input, fieldName, violations, 0); } else { sanitized = input; } return { success: violations.filter(v => v.severity === 'critical').length === 0, sanitizedData: sanitized, originalData: input, violations, sanitizationTime: Date.now() - startTime }; } catch (error) { return { success: false, originalData: input, violations: [{ field: fieldName, violationType: 'malformed', originalValue: input, severity: 'critical', description: `Sanitization error: ${error instanceof Error ? error.message : String(error)}` }], sanitizationTime: Date.now() - startTime }; } } getSanitizationStatistics() { const violationsByType = {}; const violationsBySeverity = {}; for (const violation of this.violations) { violationsByType[violation.violationType] = (violationsByType[violation.violationType] || 0) + 1; violationsBySeverity[violation.severity] = (violationsBySeverity[violation.severity] || 0) + 1; } return { totalViolations: this.violations.length, violationsByType, violationsBySeverity, recentViolations: this.violations.slice(-100) }; } updateConfig(config) { this.config = { ...this.config, ...config }; logger.info({ config: this.config }, 'Data sanitizer configuration updated'); } clearViolationHistory() { this.violations = []; logger.info('Data sanitizer violation history cleared'); } shutdown() { this.violations = []; logger.info('Data Sanitizer shutdown'); } } export async function sanitizeTask(task) { const sanitizer = DataSanitizer.getInstance(); return sanitizer.sanitizeTask(task); } export async function sanitizeInput(input, fieldName) { const sanitizer = DataSanitizer.getInstance(); return sanitizer.sanitizeInput(input, fieldName); } export function getDataSanitizer() { return DataSanitizer.getInstance(); }