endpoint-sentinel
Version:
User-friendly security scanner with interactive setup that scales from beginner to expert
317 lines • 11.2 kB
JavaScript
;
/**
* 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