/**
* Input validation and sanitization utilities
*/
import { ALLOWED_COMMANDS } from "../constants.js";
/**
* Validate command is in allowlist
*/
export function validateCommand(command: string): boolean {
const baseCommand = command.split(/\s+/)[0] || "";
return ALLOWED_COMMANDS.has(baseCommand);
}
/**
* Validate IP address (IPv4)
*/
export function validateIPv4(ip: string): boolean {
const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
const match = ip.match(ipv4Regex);
if (!match) {
return false;
}
// Check each octet is 0-255
for (let i = 1; i <= 4; i++) {
const octet = parseInt(match[i]!, 10);
if (octet < 0 || octet > 255) {
return false;
}
}
return true;
}
/**
* Validate CIDR notation
*/
export function validateCIDR(cidr: string): boolean {
const cidrRegex = /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/(\d{1,2})$/;
const match = cidr.match(cidrRegex);
if (!match) {
return false;
}
const [, ip, prefix] = match;
const prefixNum = parseInt(prefix!, 10);
return validateIPv4(ip!) && prefixNum >= 0 && prefixNum <= 32;
}
/**
* Validate hostname
*/
export function validateHostname(hostname: string): boolean {
// RFC 1123 hostname validation
const hostnameRegex = /^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
if (hostname.length > 253) {
return false;
}
return hostnameRegex.test(hostname);
}
/**
* Validate target (IP, CIDR, or hostname)
*/
export function validateTarget(target: string): boolean {
// Check for shell metacharacters
if (hasShellMetacharacters(target)) {
return false;
}
return validateIPv4(target) || validateCIDR(target) || validateHostname(target);
}
/**
* Validate URL
*/
export function validateURL(url: string): boolean {
try {
const parsed = new URL(url);
return ["http:", "https:"].includes(parsed.protocol);
} catch {
return false;
}
}
/**
* Validate port number
*/
export function validatePort(port: number | string): boolean {
const portNum = typeof port === "string" ? parseInt(port, 10) : port;
return Number.isInteger(portNum) && portNum >= 1 && portNum <= 65535;
}
/**
* Validate port range (e.g., "80", "80-443", "22,80,443")
*/
export function validatePortRange(ports: string): boolean {
if (ports === "-") {
return true; // All ports
}
const parts = ports.split(",");
for (const part of parts) {
const trimmed = part.trim();
if (trimmed.includes("-")) {
const [start, end] = trimmed.split("-").map((p) => p.trim());
if (!start || !end) {
return false;
}
const startPort = parseInt(start, 10);
const endPort = parseInt(end, 10);
if (!validatePort(startPort) || !validatePort(endPort) || startPort > endPort) {
return false;
}
} else {
const port = parseInt(trimmed, 10);
if (!validatePort(port)) {
return false;
}
}
}
return true;
}
/**
* Validate file path (prevent directory traversal)
*/
export function validatePath(path: string): boolean {
// No null bytes
if (path.includes("\0")) {
return false;
}
// No parent directory references
if (path.includes("..")) {
return false;
}
// No absolute paths to sensitive directories
const sensitivePaths = ["/etc/shadow", "/etc/passwd", "/root", "/home"];
for (const sensitive of sensitivePaths) {
if (path.startsWith(sensitive)) {
return false;
}
}
return true;
}
/**
* Check for shell metacharacters
*/
export function hasShellMetacharacters(input: string): boolean {
const metachars = /[;&|`$(){}[\]<>\\'"*?!]/;
return metachars.test(input);
}
/**
* Sanitize argument by removing dangerous characters
*/
export function sanitizeArg(arg: string): string {
// Remove shell metacharacters
return arg.replace(/[;&|`$(){}[\]<>\\'"*?!]/g, "");
}
/**
* Validate interface name (for network tools)
*/
export function validateInterface(iface: string): boolean {
// Common interface naming patterns
const ifaceRegex = /^(eth|wlan|en|wl|lo|br|veth|docker|tun|tap)\d+$/;
return ifaceRegex.test(iface);
}
/**
* Validate MAC address
*/
export function validateMAC(mac: string): boolean {
const macRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
return macRegex.test(mac);
}
/**
* Validate BSSID (same as MAC address)
*/
export function validateBSSID(bssid: string): boolean {
return validateMAC(bssid);
}
/**
* Validate domain name
*/
export function validateDomain(domain: string): boolean {
const domainRegex = /^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
return domainRegex.test(domain) && domain.length <= 253;
}
/**
* Validate email address
*/
export function validateEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
/**
* Validate hash format
*/
export function validateHash(hash: string, type?: string): boolean {
if (hasShellMetacharacters(hash)) {
return false;
}
// Basic hex validation
if (type === "md5") {
return /^[a-fA-F0-9]{32}$/.test(hash);
} else if (type === "sha1") {
return /^[a-fA-F0-9]{40}$/.test(hash);
} else if (type === "sha256") {
return /^[a-fA-F0-9]{64}$/.test(hash);
}
// Generic: must be hex
return /^[a-fA-F0-9]+$/.test(hash);
}
/**
* Validate username (alphanumeric + common chars)
*/
export function validateUsername(username: string): boolean {
// Allow alphanumeric, underscore, dash, dot, @ symbol
const usernameRegex = /^[a-zA-Z0-9._@-]{1,64}$/;
return usernameRegex.test(username);
}
/**
* Validate CVE identifier
*/
export function validateCVE(cve: string): boolean {
const cveRegex = /^CVE-\d{4}-\d{4,}$/i;
return cveRegex.test(cve);
}
/**
* Validate exploit DB ID
*/
export function validateExploitID(id: string): boolean {
return /^\d+$/.test(id);
}
/**
* Check if path exists within allowed directories
*/
export function isWithinAllowedDirectory(path: string, allowedDirs: string[]): boolean {
const normalizedPath = path.replace(/\/+/g, "/");
for (const dir of allowedDirs) {
const normalizedDir = dir.replace(/\/+/g, "/");
if (normalizedPath.startsWith(normalizedDir)) {
return true;
}
}
return false;
}
/**
* Validate wordlist path
*/
export function validateWordlistPath(path: string): boolean {
if (!validatePath(path)) {
return false;
}
// Must be under common wordlist directories
const allowedDirs = [
"/usr/share/wordlists",
"/usr/share/seclists",
"/usr/share/dirb",
"/usr/share/dirbuster",
"/tmp",
"/opt",
];
return isWithinAllowedDirectory(path, allowedDirs);
}