/** * Input Validator - Comprehensive Input Validation * * Provides Zod-based validation schemas for all security-critical inputs. * * Security Properties: * - Type-safe validation * - Custom error messages * - Sanitization transforms * - Reusable schemas * * @module v3/security/input-validator */ import { z } from 'zod'; /** * Custom error map for security-focused messages */ const securityErrorMap = (issue, ctx) => { switch (issue.code) { case z.ZodIssueCode.too_big: return { message: `Input exceeds maximum allowed size` }; case z.ZodIssueCode.too_small: return { message: `Input below minimum required size` }; case z.ZodIssueCode.invalid_string: if (issue.validation === 'email') { return { message: 'Invalid email format' }; } if (issue.validation === 'url') { return { message: 'Invalid URL format' }; } if (issue.validation === 'uuid') { return { message: 'Invalid UUID format' }; } return { message: 'Invalid string format' }; default: return { message: ctx.defaultError }; } }; // Apply custom error map globally for this module z.setErrorMap(securityErrorMap); /** * Common validation patterns as reusable regex */ const PATTERNS = { // Safe identifier: alphanumeric with underscore/hyphen SAFE_IDENTIFIER: /^[a-zA-Z][a-zA-Z0-9_-]*$/, // Safe filename: alphanumeric with dot, underscore, hyphen SAFE_FILENAME: /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/, // Safe path segment: no traversal SAFE_PATH_SEGMENT: /^[^<>:"|?*\x00-\x1f]+$/, // No shell metacharacters NO_SHELL_CHARS: /^[^;&|`$(){}><\n\r\0]+$/, // Semantic version SEMVER: /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/, }; /** * Validation limits */ const LIMITS = { MIN_PASSWORD_LENGTH: 8, MAX_PASSWORD_LENGTH: 128, MAX_EMAIL_LENGTH: 254, MAX_IDENTIFIER_LENGTH: 64, MAX_PATH_LENGTH: 4096, MAX_CONTENT_LENGTH: 1024 * 1024, // 1MB MAX_ARRAY_LENGTH: 1000, MAX_OBJECT_KEYS: 100, }; // ============================================================================ // Base Validation Schemas // ============================================================================ /** * Safe string that cannot contain shell metacharacters */ export const SafeStringSchema = z.string() .min(1, 'String cannot be empty') .max(LIMITS.MAX_CONTENT_LENGTH, 'String too long') .regex(PATTERNS.NO_SHELL_CHARS, 'String contains invalid characters'); /** * Safe identifier for IDs, names, etc. */ export const IdentifierSchema = z.string() .min(1, 'Identifier cannot be empty') .max(LIMITS.MAX_IDENTIFIER_LENGTH, 'Identifier too long') .regex(PATTERNS.SAFE_IDENTIFIER, 'Invalid identifier format'); /** * Safe filename */ export const FilenameSchema = z.string() .min(1, 'Filename cannot be empty') .max(255, 'Filename too long') .regex(PATTERNS.SAFE_FILENAME, 'Invalid filename format'); /** * Email schema with length limit */ export const EmailSchema = z.string() .email('Invalid email format') .max(LIMITS.MAX_EMAIL_LENGTH, 'Email too long') .toLowerCase(); /** * Password schema with complexity requirements */ export const PasswordSchema = z.string() .min(LIMITS.MIN_PASSWORD_LENGTH, `Password must be at least ${LIMITS.MIN_PASSWORD_LENGTH} characters`) .max(LIMITS.MAX_PASSWORD_LENGTH, `Password must not exceed ${LIMITS.MAX_PASSWORD_LENGTH} characters`) .refine((val) => /[A-Z]/.test(val), 'Password must contain uppercase letter') .refine((val) => /[a-z]/.test(val), 'Password must contain lowercase letter') .refine((val) => /\d/.test(val), 'Password must contain digit'); /** * UUID schema */ export const UUIDSchema = z.string().uuid('Invalid UUID format'); /** * URL schema with HTTPS enforcement */ export const HttpsUrlSchema = z.string() .url('Invalid URL format') .refine((val) => val.startsWith('https://'), 'URL must use HTTPS'); /** * URL schema (allows HTTP for development) */ export const UrlSchema = z.string() .url('Invalid URL format'); /** * Semantic version schema */ export const SemverSchema = z.string() .regex(PATTERNS.SEMVER, 'Invalid semantic version format'); /** * Port number schema */ export const PortSchema = z.number() .int('Port must be an integer') .min(1, 'Port must be at least 1') .max(65535, 'Port must be at most 65535'); /** * IP address schema (v4) */ export const IPv4Schema = z.string() .ip({ version: 'v4', message: 'Invalid IPv4 address' }); /** * IP address schema (v4 or v6) */ export const IPSchema = z.string() .ip({ message: 'Invalid IP address' }); // ============================================================================ // Authentication Schemas // ============================================================================ /** * User role schema */ export const UserRoleSchema = z.enum([ 'admin', 'operator', 'developer', 'viewer', 'service', ]); /** * Permission schema */ export const PermissionSchema = z.enum([ 'swarm.create', 'swarm.read', 'swarm.update', 'swarm.delete', 'swarm.scale', 'agent.spawn', 'agent.read', 'agent.terminate', 'task.create', 'task.read', 'task.cancel', 'metrics.read', 'system.admin', 'api.access', ]); /** * Login request schema */ export const LoginRequestSchema = z.object({ email: EmailSchema, password: z.string().min(1, 'Password is required'), mfaCode: z.string().length(6, 'MFA code must be 6 digits').optional(), }); /** * User creation schema */ export const CreateUserSchema = z.object({ email: EmailSchema, password: PasswordSchema, role: UserRoleSchema, permissions: z.array(PermissionSchema).optional(), isActive: z.boolean().optional().default(true), }); /** * API key creation schema */ export const CreateApiKeySchema = z.object({ name: IdentifierSchema, permissions: z.array(PermissionSchema).optional(), expiresAt: z.date().optional(), }); // ============================================================================ // Agent & Task Schemas // ============================================================================ /** * Agent type schema */ export const AgentTypeSchema = z.enum([ 'coder', 'reviewer', 'tester', 'planner', 'researcher', 'security-architect', 'security-auditor', 'memory-specialist', 'swarm-specialist', 'integration-architect', 'performance-engineer', 'core-architect', 'test-architect', 'queen-coordinator', 'project-coordinator', ]); /** * Agent spawn request schema */ export const SpawnAgentSchema = z.object({ type: AgentTypeSchema, id: IdentifierSchema.optional(), config: z.record(z.unknown()).optional(), timeout: z.number().positive().optional(), }); /** * Task input schema */ export const TaskInputSchema = z.object({ taskId: UUIDSchema, content: SafeStringSchema.max(10000, 'Task content too long'), agentType: AgentTypeSchema, priority: z.enum(['low', 'medium', 'high', 'critical']).optional(), metadata: z.record(z.unknown()).optional(), }); // ============================================================================ // Command & Path Schemas // ============================================================================ /** * Command argument schema */ export const CommandArgumentSchema = z.string() .max(1024, 'Argument too long') .refine((val) => !val.includes('\0'), 'Argument contains null byte') .refine((val) => !/[;&|`$(){}><]/.test(val), 'Argument contains shell metacharacters'); /** * Path schema */ export const PathSchema = z.string() .max(LIMITS.MAX_PATH_LENGTH, 'Path too long') .refine((val) => !val.includes('\0'), 'Path contains null byte') .refine((val) => !val.includes('..'), 'Path contains traversal pattern'); // ============================================================================ // Configuration Schemas // ============================================================================ /** * Security configuration schema */ export const SecurityConfigSchema = z.object({ bcryptRounds: z.number().int().min(10).max(20).default(12), jwtExpiresIn: z.string().default('24h'), sessionTimeout: z.number().positive().default(3600000), maxLoginAttempts: z.number().int().positive().default(5), lockoutDuration: z.number().positive().default(900000), requireMFA: z.boolean().default(false), }); /** * Executor configuration schema */ export const ExecutorConfigSchema = z.object({ allowedCommands: z.array(IdentifierSchema).min(1), blockedPatterns: z.array(z.string()).optional(), timeout: z.number().positive().default(30000), maxBuffer: z.number().positive().default(10 * 1024 * 1024), cwd: PathSchema.optional(), allowSudo: z.boolean().default(false), }); // ============================================================================ // Sanitization Functions // ============================================================================ /** * Sanitizes a string by removing dangerous characters */ export function sanitizeString(input) { return input .replace(/\0/g, '') // Remove null bytes .replace(/[<>]/g, '') // Remove HTML brackets .replace(/javascript:/gi, '') // Remove javascript: protocol .replace(/data:/gi, '') // Remove data: protocol .trim(); } /** * Sanitizes HTML entities */ export function sanitizeHtml(input) { return input .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } /** * Sanitizes a path by removing traversal patterns */ export function sanitizePath(input) { return input .replace(/\0/g, '') // Remove null bytes .replace(/\.\./g, '') // Remove traversal patterns .replace(/\/+/g, '/') // Normalize slashes .replace(/^\//, '') // Remove leading slash .trim(); } // ============================================================================ // Validation Helper Class // ============================================================================ export class InputValidator { /** * Validates input against a schema */ static validate(schema, input) { return schema.parse(input); } /** * Safely validates input, returning result */ static safeParse(schema, input) { return schema.safeParse(input); } /** * Validates email */ static validateEmail(email) { return EmailSchema.parse(email); } /** * Validates password */ static validatePassword(password) { return PasswordSchema.parse(password); } /** * Validates identifier */ static validateIdentifier(id) { return IdentifierSchema.parse(id); } /** * Validates path */ static validatePath(path) { return PathSchema.parse(path); } /** * Validates command argument */ static validateCommandArg(arg) { return CommandArgumentSchema.parse(arg); } /** * Validates login request */ static validateLoginRequest(data) { return LoginRequestSchema.parse(data); } /** * Validates user creation request */ static validateCreateUser(data) { return CreateUserSchema.parse(data); } /** * Validates task input */ static validateTaskInput(data) { return TaskInputSchema.parse(data); } } // ============================================================================ // Export all schemas for direct use // ============================================================================ export { z, PATTERNS, LIMITS, }; //# sourceMappingURL=input-validator.js.map