UNPKG

endpoint-sentinel

Version:

User-friendly security scanner with interactive setup that scales from beginner to expert

317 lines 11.2 kB
"use strict"; /** * Input Validation Utilities * Comprehensive validation for CLI inputs and configuration */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.validateTarget = validateTarget; exports.validateKeywords = validateKeywords; exports.validateOutput = validateOutput; exports.validateCookie = validateCookie; exports.validateRateLimit = validateRateLimit; exports.validateTimeout = validateTimeout; exports.sanitizeInput = sanitizeInput; exports.validateScanConfig = validateScanConfig; const url_1 = require("url"); const path = __importStar(require("path")); const fs = __importStar(require("fs")); /** * Normalizes URL by adding protocol if missing */ function normalizeUrl(target) { if (!target || typeof target !== 'string') { return target; } // If URL already has a protocol, return as-is if (/^https?:\/\//i.test(target)) { return target; } // If it looks like a domain/hostname, prepend https:// if (/^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(\/.*)?$/.test(target)) { return `https://${target}`; } // Return as-is for other cases (will likely fail validation) return target; } /** * Validates target URL for security scanning */ function validateTarget(target) { if (!target || typeof target !== 'string') { return { isValid: false, error: 'Target URL is required' }; } try { const normalizedTarget = normalizeUrl(target); const url = new url_1.URL(normalizedTarget); // Ensure protocol is HTTP or HTTPS if (!['http:', 'https:'].includes(url.protocol)) { return { isValid: false, error: 'Target must use HTTP or HTTPS protocol' }; } // Prevent scanning localhost or private IPs unless explicitly allowed if (isPrivateNetwork(url.hostname)) { return { isValid: false, error: 'Scanning private networks requires explicit authorization' }; } // Validate hostname if (!url.hostname) { return { isValid: false, error: 'Target URL must have a valid hostname' }; } // Check for suspicious patterns that might indicate malicious URLs if (containsSuspiciousPatterns(target)) { return { isValid: false, error: 'Target URL contains suspicious patterns' }; } return { isValid: true }; } catch (error) { return { isValid: false, error: 'Invalid URL format' }; } } /** * Validates keyword string and parses into array */ function validateKeywords(keywordString) { if (!keywordString || typeof keywordString !== 'string') { return { isValid: false, error: 'Keywords must be a string' }; } const keywords = keywordString .split(',') .map(k => k.trim()) .filter(k => k.length > 0); if (keywords.length === 0) { return { isValid: false, error: 'At least one keyword is required' }; } // Validate individual keywords for (const keyword of keywords) { if (keyword.length < 2) { return { isValid: false, error: 'Keywords must be at least 2 characters long' }; } if (keyword.length > 50) { return { isValid: false, error: 'Keywords cannot exceed 50 characters' }; } if (!/^[a-zA-Z0-9_-]+$/.test(keyword)) { return { isValid: false, error: 'Keywords can only contain letters, numbers, hyphens, and underscores' }; } } return { isValid: true, keywords }; } /** * Validates output file path */ function validateOutput(outputPath) { if (!outputPath || typeof outputPath !== 'string') { return { isValid: false, error: 'Output path is required' }; } try { // Resolve absolute path const absolutePath = path.resolve(outputPath); const directory = path.dirname(absolutePath); const extension = path.extname(absolutePath).toLowerCase(); // Check if directory exists or can be created if (!fs.existsSync(directory)) { try { fs.mkdirSync(directory, { recursive: true }); } catch { return { isValid: false, error: 'Cannot create output directory' }; } } // Validate file extension const allowedExtensions = ['.json', '.csv', '.txt', '.log']; if (extension && !allowedExtensions.includes(extension)) { return { isValid: false, error: `Unsupported file extension. Allowed: ${allowedExtensions.join(', ')}` }; } // Check write permissions (if file exists) if (fs.existsSync(absolutePath)) { try { fs.accessSync(absolutePath, fs.constants.W_OK); } catch { return { isValid: false, error: 'Cannot write to output file (permission denied)' }; } } return { isValid: true }; } catch (error) { return { isValid: false, error: 'Invalid output path' }; } } /** * Validates cookie string format */ function validateCookie(cookieString) { if (!cookieString || typeof cookieString !== 'string') { return { isValid: false, error: 'Cookie string is required' }; } // Basic cookie format validation const cookiePairs = cookieString.split(';').map(c => c.trim()); for (const pair of cookiePairs) { if (!pair.includes('=')) { return { isValid: false, error: 'Invalid cookie format: each cookie must have name=value format' }; } const [name, ...valueParts] = pair.split('='); const value = valueParts.join('='); if (!name || !name.trim()) { return { isValid: false, error: 'Cookie name cannot be empty' }; } // Check for potentially dangerous cookie values if (value && containsSuspiciousPatterns(value)) { return { isValid: false, error: 'Cookie value contains suspicious patterns' }; } } return { isValid: true }; } /** * Validates rate limit value */ function validateRateLimit(rateLimit) { if (typeof rateLimit !== 'number' || isNaN(rateLimit)) { return { isValid: false, error: 'Rate limit must be a number' }; } if (rateLimit <= 0) { return { isValid: false, error: 'Rate limit must be positive' }; } if (rateLimit > 100) { return { isValid: false, error: 'Rate limit cannot exceed 100 requests per second (ethical scanning)' }; } return { isValid: true }; } /** * Validates timeout value */ function validateTimeout(timeout) { if (typeof timeout !== 'number' || isNaN(timeout)) { return { isValid: false, error: 'Timeout must be a number' }; } if (timeout <= 0) { return { isValid: false, error: 'Timeout must be positive' }; } if (timeout > 300000) { // 5 minutes return { isValid: false, error: 'Timeout cannot exceed 5 minutes' }; } return { isValid: true }; } /** * Checks if hostname is in private network range */ function isPrivateNetwork(hostname) { // Allow localhost explicitly if (hostname === 'localhost' || hostname === '127.0.0.1') { return false; // Allow for testing } // Private IP ranges const privateRanges = [ /^10\./, // 10.0.0.0/8 /^172\.(1[6-9]|2[0-9]|3[01])\./, // 172.16.0.0/12 /^192\.168\./, // 192.168.0.0/16 /^169\.254\./, // 169.254.0.0/16 (link-local) /^fc00:/, // IPv6 unique local /^fe80:/ // IPv6 link-local ]; return privateRanges.some(range => range.test(hostname)); } /** * Checks for suspicious patterns in input */ function containsSuspiciousPatterns(input) { const suspiciousPatterns = [ /<script/i, // Script injection /javascript:/i, // JavaScript protocol /data:.*base64/i, // Data URLs with base64 /\.\./, // Path traversal /%[0-9a-f]{2}/i, // URL encoding (potential obfuscation) /[\x00-\x1f\x7f]/, // Control characters /[<>'"&]/ // HTML/XML special characters ]; return suspiciousPatterns.some(pattern => pattern.test(input)); } /** * Sanitizes user input by removing dangerous characters */ function sanitizeInput(input) { if (!input || typeof input !== 'string') { return ''; } return input .replace(/[\x00-\x1f\x7f]/g, '') // Remove control characters .replace(/[<>'"&]/g, '') // Remove HTML/XML special characters .trim(); } /** * Validates entire scan configuration */ function validateScanConfig(config) { // Validate target const targetResult = validateTarget(config.target); if (!targetResult.isValid) { return targetResult; } // Validate keywords if provided if (config.keywords) { const keywordResult = validateKeywords(config.keywords); if (!keywordResult.isValid) { return keywordResult; } } // Validate cookie if provided if (config.cookie) { const cookieResult = validateCookie(config.cookie); if (!cookieResult.isValid) { return cookieResult; } } // Validate output if provided if (config.output) { const outputResult = validateOutput(config.output); if (!outputResult.isValid) { return outputResult; } } // Validate rate limit if provided if (config.rateLimit !== undefined) { const rateLimitResult = validateRateLimit(config.rateLimit); if (!rateLimitResult.isValid) { return rateLimitResult; } } // Validate timeout if provided if (config.timeout !== undefined) { const timeoutResult = validateTimeout(config.timeout); if (!timeoutResult.isValid) { return timeoutResult; } } return { isValid: true }; } //# sourceMappingURL=validation.js.map