UNPKG

@probelabs/probe

Version:

Node.js wrapper for the probe code search tool

200 lines (179 loc) 5.13 kB
/** * Unified command parsing utilities for bash tool * * This module provides a single source of truth for parsing shell commands. * It supports only simple commands (no pipes, operators, or substitutions) * to align with the executor's capabilities. * * @module bashCommandUtils */ /** * Parse a simple shell command into command and arguments * Properly handles quoted arguments and strips quotes * Rejects complex shell constructs for security and consistency * * @param {string} command - Command string to parse * @returns {Object} Parse result with command, args, and validation info */ export function parseSimpleCommand(command) { if (!command || typeof command !== 'string') { return { success: false, error: 'Command must be a non-empty string', command: null, args: [], isComplex: false }; } const trimmed = command.trim(); if (!trimmed) { return { success: false, error: 'Command cannot be empty', command: null, args: [], isComplex: false }; } // Check for complex shell constructs that we don't support const complexPatterns = [ /\|/, // Pipes /&&/, // Logical AND /\|\|/, // Logical OR /(?<!\\);/, // Command separator (but not escaped \;) /&$/, // Background execution /\$\(/, // Command substitution $() /`/, // Command substitution `` />/, // Redirection > /</, // Redirection < /\*\*/, // Glob patterns (potentially dangerous) /^\s*\{.*,.*\}|\{.*\.\.\.*\}/, // Brace expansion like {a,b} or {1..10} (but not find {} placeholders) ]; for (const pattern of complexPatterns) { if (pattern.test(trimmed)) { return { success: false, error: 'Complex shell commands with pipes, operators, or redirections are not supported for security reasons', command: null, args: [], isComplex: true, detected: pattern.toString() }; } } // Parse simple command with proper quote handling const args = []; let current = ''; let inQuotes = false; let quoteChar = ''; let escaped = false; for (let i = 0; i < trimmed.length; i++) { const char = trimmed[i]; const nextChar = i + 1 < trimmed.length ? trimmed[i + 1] : ''; if (escaped) { // Handle escaped characters current += char; escaped = false; continue; } if (char === '\\' && !inQuotes) { // Escape next character escaped = true; continue; } if (!inQuotes && (char === '"' || char === "'")) { // Start quoted section - don't include quote char inQuotes = true; quoteChar = char; } else if (inQuotes && char === quoteChar) { // End quoted section - don't include quote char inQuotes = false; quoteChar = ''; } else if (!inQuotes && char === ' ') { // Space outside quotes - end current argument if (current.trim()) { args.push(current.trim()); current = ''; } } else { // Regular character - add to current argument current += char; } } // Add final argument if exists if (current.trim()) { args.push(current.trim()); } // Check for unclosed quotes if (inQuotes) { return { success: false, error: `Unclosed quote in command: ${quoteChar}`, command: null, args: [], isComplex: false }; } if (args.length === 0) { return { success: false, error: 'No command found after parsing', command: null, args: [], isComplex: false }; } const [baseCommand, ...commandArgs] = args; return { success: true, error: null, command: baseCommand, args: commandArgs, fullArgs: args, isComplex: false, original: command }; } /** * Check if a command contains complex shell constructs * @param {string} command - Command to check * @returns {boolean} True if command is complex */ export function isComplexCommand(command) { const result = parseSimpleCommand(command); return result.isComplex; } /** * Legacy compatibility function - parses command for permission checking * @param {string} command - Command to parse * @returns {Object} Parse result compatible with existing permission checker */ export function parseCommand(command) { const result = parseSimpleCommand(command); if (!result.success) { return { command: '', args: [], error: result.error, isComplex: result.isComplex }; } return { command: result.command, args: result.args, error: null, isComplex: result.isComplex }; } /** * Parse command for execution - returns array format expected by spawn() * @param {string} command - Command to parse * @returns {string[]|null} Array of [command, ...args] or null if invalid */ export function parseCommandForExecution(command) { const result = parseSimpleCommand(command); if (!result.success) { return null; } return result.fullArgs; }