// Security middleware for input validation and CSRF protection /** * Input validation middleware * Validates common input patterns and sanitizes data */ function validateInput(req, res, next) { // Sanitize query parameters for (const key in req.query) { if (typeof req.query[key] === 'string') { // Remove control characters req.query[key] = req.query[key].replace(/[\x00-\x1F\x7F]/g, ''); // Limit length if (req.query[key].length > 1000) { req.query[key] = req.query[key].substring(0, 1000); } } } // Sanitize body data for non-JSON requests if (req.body && typeof req.body === 'object' && !Array.isArray(req.body)) { for (const key in req.body) { if (typeof req.body[key] === 'string') { // Remove control characters but preserve newlines for textareas if (key.includes('description') || key.includes('content') || key.includes('motd')) { req.body[key] = req.body[key].replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ''); } else { req.body[key] = req.body[key].replace(/[\x00-\x1F\x7F]/g, ''); } // Limit length based on field type const maxLength = getMaxLengthForField(key); if (req.body[key].length > maxLength) { req.body[key] = req.body[key].substring(0, maxLength); } } } } next(); } /** * Get maximum allowed length for different field types */ function getMaxLengthForField(fieldName) { const fieldLimits = { // User authentication fields 'username': 50, 'password': 200, 'confirmPassword': 200, 'currentPassword': 200, 'newPassword': 200, // Server/world names 'name': 100, 'worldName': 100, 'serverName': 200, // Text content 'description': 2000, 'motd': 500, 'content': 5000, // Commands and paths 'command': 500, 'path': 500, // Network settings 'bind': 100, 'serverlist_url': 500, // Default 'default': 200 }; return fieldLimits[fieldName] || fieldLimits['default']; } /** * XSS protection middleware * Escapes HTML in user input for specific fields */ function xssProtection(req, res, next) { if (req.body && typeof req.body === 'object') { // Fields that should be HTML escaped const fieldsToEscape = ['username', 'name', 'worldName', 'serverName', 'motd']; for (const field of fieldsToEscape) { if (req.body[field] && typeof req.body[field] === 'string') { req.body[field] = escapeHtml(req.body[field]); } } } next(); } /** * Basic HTML escape function */ function escapeHtml(unsafe) { return unsafe .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } /** * Security headers middleware (additional to helmet) */ function additionalSecurityHeaders(req, res, next) { // Prevent MIME type sniffing res.setHeader('X-Content-Type-Options', 'nosniff'); // Prevent clickjacking res.setHeader('X-Frame-Options', 'DENY'); // XSS protection res.setHeader('X-XSS-Protection', '1; mode=block'); // Referrer policy res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin'); next(); } /** * Request size validation */ function validateRequestSize(req, res, next) { // Check for unusually large requests that might indicate an attack const contentLength = req.headers['content-length']; if (contentLength && parseInt(contentLength) > 50 * 1024 * 1024) { // 50MB limit return res.status(413).json({ error: 'Request too large' }); } next(); } /** * Path traversal protection */ function pathTraversalProtection(req, res, next) { // Check for path traversal attempts in various parameters const suspiciousPatterns = ['../', '..\\', '%2e%2e%2f', '%2e%2e%5c']; function checkForTraversal(obj, path = '') { for (const [key, value] of Object.entries(obj)) { if (typeof value === 'string') { const lowerValue = value.toLowerCase(); for (const pattern of suspiciousPatterns) { if (lowerValue.includes(pattern)) { console.warn(`Path traversal attempt detected: ${path}${key} = ${value}`); return true; } } } else if (typeof value === 'object' && value !== null) { if (checkForTraversal(value, `${path}${key}.`)) { return true; } } } return false; } if ((req.query && checkForTraversal(req.query)) || (req.body && checkForTraversal(req.body))) { return res.status(400).json({ error: 'Invalid request parameters' }); } next(); } module.exports = { validateInput, xssProtection, additionalSecurityHeaders, validateRequestSize, pathTraversalProtection, escapeHtml };