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
JavaScript
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();
}