Skip to main content
Glama
validators.js6.17 kB
// validators.js - Input Validation and Sanitization const { z } = require('zod'); const os = require('os'); const path = require('path'); const config = require('./config'); /** * Get valid network interfaces from the system */ function getValidInterfaces() { const interfaces = os.networkInterfaces(); const validInterfaces = Object.keys(interfaces); // Filter by allowed interfaces if configured if (config.network.allowedInterfaces.length > 0) { return validInterfaces.filter(iface => config.network.allowedInterfaces.includes(iface) ); } return validInterfaces; } /** * Validate IPv4 address with proper range checking */ function isValidIPv4(ip) { const parts = ip.split('.'); if (parts.length !== 4) return false; return parts.every(part => { const num = parseInt(part, 10); return num >= 0 && num <= 255 && part === num.toString(); }); } /** * Validate network interface name */ const interfaceSchema = z.string() .min(1, 'Interface name cannot be empty') .max(15, 'Interface name too long') .regex(/^[a-zA-Z0-9_-]+$/, 'Interface name contains invalid characters') .refine( (iface) => getValidInterfaces().includes(iface), (iface) => ({ message: `Invalid interface. Available: ${getValidInterfaces().join(', ')}` }) ); /** * Validate capture duration */ const durationSchema = z.number() .int('Duration must be an integer') .min(config.security.minCaptureDuration, `Duration must be at least ${config.security.minCaptureDuration} second(s)`) .max(config.security.maxCaptureDuration, `Duration cannot exceed ${config.security.maxCaptureDuration} seconds`) .finite('Duration must be finite'); /** * Validate IPv4 address */ const ipv4Schema = z.string() .min(7, 'IP address too short') .max(15, 'IP address too long') .regex(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, 'Invalid IP address format') .refine(isValidIPv4, 'Invalid IP address (octets must be 0-255)'); /** * Validate PCAP file path */ function validatePcapPath(pcapPath) { // Basic validation if (!pcapPath || typeof pcapPath !== 'string') { throw new Error('PCAP path must be a non-empty string'); } if (pcapPath.length > 4096) { throw new Error('PCAP path too long'); } // Check for path traversal attempts if (pcapPath.includes('..')) { throw new Error('Path traversal detected'); } // Validate characters (allow alphanumeric, /, _, -, .) if (!/^[a-zA-Z0-9/_.-]+$/.test(pcapPath)) { throw new Error('PCAP path contains invalid characters'); } // Check file extension const ext = path.extname(pcapPath).toLowerCase(); if (ext !== '.pcap' && ext !== '.pcapng') { throw new Error('Invalid file extension (must be .pcap or .pcapng)'); } // Resolve to absolute path const resolvedPath = path.resolve(pcapPath); // Check if within allowed directories const isAllowed = config.security.allowedPcapDirs.some(allowedDir => resolvedPath.startsWith(allowedDir) ); if (!isAllowed) { throw new Error( `Access denied: File must be in one of these directories: ${config.security.allowedPcapDirs.join(', ')}` ); } return resolvedPath; } /** * Sanitize packet data to remove sensitive information */ function sanitizePacketData(packets) { if (!config.security.sanitizeSensitiveData) { return packets; } const sensitiveFields = [ 'http.authorization', 'http.cookie', 'http.set_cookie', 'http.proxy_authorization', 'ftp.request.arg', 'telnet.data', 'smtp.auth.password', 'imap.auth.password', 'pop.auth.password' ]; return packets.map(packet => { if (!packet._source?.layers) return packet; const sanitized = JSON.parse(JSON.stringify(packet)); sensitiveFields.forEach(field => { if (sanitized._source.layers[field]) { sanitized._source.layers[field] = ['[REDACTED]']; } }); return sanitized; }); } /** * Sanitize error messages for user display */ function sanitizeError(error, includeDetails = false) { // In development, show full errors if (config.server.environment === 'development' && includeDetails) { return error.message; } // In production, use generic messages for security const errorMap = { 'EACCES': 'Permission denied', 'ENOENT': 'File or resource not found', 'EPERM': 'Operation not permitted', 'ETIMEDOUT': 'Operation timed out', 'ECONNREFUSED': 'Connection refused' }; // Check for known error codes if (error.code && errorMap[error.code]) { return errorMap[error.code]; } // Check for validation errors (safe to show) if (error.name === 'ZodError') { return `Validation error: ${error.errors[0]?.message || 'Invalid input'}`; } // Generic message for unknown errors return 'An error occurred during the operation'; } /** * Validate and sanitize all tool arguments */ const validators = { capturePackets: z.object({ interface: interfaceSchema.optional().default(config.network.defaultInterface), duration: durationSchema.optional().default(5) }), getSummaryStats: z.object({ interface: interfaceSchema.optional().default(config.network.defaultInterface), duration: durationSchema.optional().default(5) }), getConversations: z.object({ interface: interfaceSchema.optional().default(config.network.defaultInterface), duration: durationSchema.optional().default(5) }), checkThreats: z.object({ interface: interfaceSchema.optional().default(config.network.defaultInterface), duration: durationSchema.optional().default(5) }), checkIpThreats: z.object({ ip: ipv4Schema }), analyzePcap: z.object({ pcapPath: z.string().min(1, 'Path cannot be empty'), includeUrls: z.boolean().optional().default(true), includeProtocols: z.boolean().optional().default(true) }), extractCredentials: z.object({ pcapPath: z.string().min(1, 'Path cannot be empty'), includeHashes: z.boolean().optional().default(false) }) }; module.exports = { validators, getValidInterfaces, validatePcapPath, sanitizePacketData, sanitizeError, isValidIPv4 };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/anishphilip012git/WireMCP-Secure'

If you have feedback or need assistance with the MCP directory API, please join our Discord server