Created
August 20, 2025 06:51
-
-
Save saleebm/6fe61a6f2b7f8974e9e62fbf807f14fc to your computer and use it in GitHub Desktop.
Input sanitizer for cli tools
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Input Sanitizer for CLI Command Execution | |
| * | |
| * This module provides sanitization for user inputs before they are passed | |
| * to CLI commands, preventing command injection attacks. | |
| */ | |
| /** | |
| * Dangerous patterns that should be blocked in inputs | |
| */ | |
| const DANGEROUS_PATTERNS = [ | |
| /[;&|`$]/g, // Command separators and substitution | |
| /\.\.\//g, // Directory traversal | |
| /^-/, // Options that could be interpreted as flags | |
| /<|>|>>/g, // Redirection operators | |
| /\n|\r/g, // Newlines that could inject commands | |
| /\$\{.*\}/g, // Template literals | |
| /\$\(.*\)/g, // Command substitution | |
| ]; | |
| /** | |
| * List of allowed characters for different input types | |
| */ | |
| const ALLOWED_CHARS = { | |
| path: /^[a-zA-Z0-9\-_./\\: ]+$/, | |
| message: /^[a-zA-Z0-9\-_.,:;!?()\[\]{}"'\s]+$/, | |
| identifier: /^[a-zA-Z0-9\-_]+$/, | |
| }; | |
| export interface SanitizationOptions { | |
| /** Type of input being sanitized */ | |
| type: 'path' | 'message' | 'identifier' | 'custom'; | |
| /** Custom allowed pattern for 'custom' type */ | |
| allowedPattern?: RegExp; | |
| /** Maximum length of input */ | |
| maxLength?: number; | |
| /** Whether to throw on invalid input or return sanitized */ | |
| strict?: boolean; | |
| } | |
| /** | |
| * Sanitize user input for CLI command execution | |
| * | |
| * @param input - The raw user input | |
| * @param options - Sanitization options | |
| * @returns Sanitized input safe for CLI execution | |
| * @throws Error if input is malicious and strict mode is enabled | |
| */ | |
| export function sanitizeInput( | |
| input: string, | |
| options: SanitizationOptions | |
| ): string { | |
| // Check for null/undefined | |
| if (!input || typeof input !== 'string') { | |
| if (options.strict) { | |
| throw new Error('Invalid input: must be a non-empty string'); | |
| } | |
| return ''; | |
| } | |
| // Trim whitespace | |
| let sanitized = input.trim(); | |
| // Check length | |
| const maxLength = options.maxLength || 1000; | |
| if (sanitized.length > maxLength) { | |
| if (options.strict) { | |
| throw new Error(`Input exceeds maximum length of ${maxLength} characters`); | |
| } | |
| sanitized = sanitized.substring(0, maxLength); | |
| } | |
| // Check for dangerous patterns | |
| for (const pattern of DANGEROUS_PATTERNS) { | |
| if (pattern.test(sanitized)) { | |
| if (options.strict) { | |
| throw new Error(`Potentially dangerous input detected: ${pattern}`); | |
| } | |
| // Remove dangerous characters | |
| sanitized = sanitized.replace(pattern, ''); | |
| } | |
| } | |
| // Apply type-specific validation | |
| if (options.type !== 'custom') { | |
| const allowedPattern = ALLOWED_CHARS[options.type]; | |
| if (allowedPattern && !allowedPattern.test(sanitized)) { | |
| if (options.strict) { | |
| throw new Error(`Input contains invalid characters for type: ${options.type}`); | |
| } | |
| // Keep only allowed characters | |
| sanitized = sanitized.split('').filter(char => | |
| allowedPattern.test(char) | |
| ).join(''); | |
| } | |
| } else if (options.allowedPattern) { | |
| if (!options.allowedPattern.test(sanitized)) { | |
| if (options.strict) { | |
| throw new Error('Input does not match allowed pattern'); | |
| } | |
| // Can't safely filter with custom pattern, return empty | |
| return ''; | |
| } | |
| } | |
| return sanitized; | |
| } | |
| /** | |
| * Sanitize a file path for safe file system operations | |
| * | |
| * @param path - The file path to sanitize | |
| * @param basePath - Optional base path to restrict access | |
| * @returns Sanitized path | |
| */ | |
| export function sanitizePath(path: string, basePath?: string): string { | |
| const sanitized = sanitizeInput(path, { | |
| type: 'path', | |
| maxLength: 500, | |
| strict: false | |
| }); | |
| // Prevent directory traversal | |
| const normalized = sanitized.replace(/\.\.\//g, '').replace(/\.\.$/g, ''); | |
| // If base path is provided, ensure the path stays within it | |
| if (basePath) { | |
| const fullPath = `${basePath}/${normalized}`.replace(/\/+/g, '/'); | |
| if (!fullPath.startsWith(basePath)) { | |
| throw new Error('Path traversal attempt detected'); | |
| } | |
| return fullPath; | |
| } | |
| return normalized; | |
| } | |
| /** | |
| * Sanitize a command message/prompt | |
| * | |
| * @param message - The message to sanitize | |
| * @returns Sanitized message | |
| */ | |
| export function sanitizeMessage(message: string): string { | |
| return sanitizeInput(message, { | |
| type: 'message', | |
| maxLength: 10000, | |
| strict: false | |
| }); | |
| } | |
| /** | |
| * Escape shell arguments for safe execution | |
| * | |
| * @param arg - The argument to escape | |
| * @returns Escaped argument safe for shell execution | |
| */ | |
| export function escapeShellArg(arg: string): string { | |
| // For Windows | |
| if (process.platform === 'win32') { | |
| return `"${arg.replace(/"/g, '""')}"`; | |
| } | |
| // For Unix-like systems | |
| return `'${arg.replace(/'/g, "'\\''")}'`; | |
| } | |
| /** | |
| * Validate and sanitize environment variables | |
| * | |
| * @param env - Environment variables object | |
| * @param allowedKeys - List of allowed environment variable keys | |
| * @returns Sanitized environment variables | |
| */ | |
| export function sanitizeEnv( | |
| env: Record<string, string | undefined>, | |
| allowedKeys: string[] | |
| ): Record<string, string | undefined> { | |
| const sanitized: Record<string, string | undefined> = {}; | |
| for (const key of allowedKeys) { | |
| if (env[key]) { | |
| // Only allow printable ASCII characters in env values | |
| const value = env[key]!.replace(/[^\x20-\x7E]/g, ''); | |
| sanitized[key] = value; | |
| } | |
| } | |
| return sanitized; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment