{"record":{"id":"53617284ced403f3","repo":"jackwener/OpenCLI","slug":"install-command-contains-unsafe-shell-operators-an","errorCode":null,"errorMessage":"Install command contains unsafe shell operators and cannot be executed securely: \"${cmd}\". Please install the tool manually.","messagePattern":"Install command contains unsafe shell operators and cannot be executed securely: \"(.+?)\"\\. Please install the tool manually\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/external.ts","lineNumber":115,"sourceCode":"export function formatExternalCliLabel(cli: ExternalCliConfig): string {\n  return cli.package && cli.package !== cli.name ? `${cli.name}(${cli.package})` : cli.name;\n}\n\n/**\n * Safely parses a command string into a binary and argument list.\n * Rejects commands containing shell operators (&&, ||, |, ;, >, <, `) that\n * cannot be safely expressed as execFileSync arguments.\n *\n * Args:\n *   cmd: Raw command string from YAML config (e.g. \"brew install gh\")\n *\n * Returns:\n *   Object with `binary` and `args` fields, or throws on unsafe input.\n */\nexport function parseCommand(cmd: string): { binary: string; args: string[] } {\n  const shellOperators = /&&|\\|\\|?|;|[><`$#\\n\\r]|\\$\\(/;\n  if (shellOperators.test(cmd)) {\n    throw new Error(\n      `Install command contains unsafe shell operators and cannot be executed securely: \"${cmd}\". ` +\n        `Please install the tool manually.`\n    );\n  }\n\n  // Tokenise respecting single- and double-quoted segments (no variable expansion).\n  const tokens: string[] = [];\n  const re = /(?:\"([^\"]*)\")|(?:'([^']*)')|(\\S+)/g;\n  let match: RegExpExecArray | null;\n  while ((match = re.exec(cmd)) !== null) {\n    tokens.push(match[1] ?? match[2] ?? match[3]);\n  }\n\n  if (tokens.length === 0) {\n    throw new Error(`Install command is empty.`);\n  }\n\n  const [binary, ...args] = tokens;","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/src/external.ts#L97-L133","documentation":"parseCommand rejects install command strings that contain shell metacharacters (&&, ||, ;, redirects, pipes, backticks, $, #, newlines, or command substitution). The library refuses to pass such commands to the shell because they could execute arbitrary code, so it throws instead of risking command injection. The user must install the tool themselves.","triggerScenarios":"Calling parseCommand (directly or via installExternalCli) with a command string containing any of &&, ||, |, ;, >, <, `, $, #, newline, or carriage return, e.g. 'npm install -g foo && bar' or 'FOO=1 npm i foo'.","commonSituations":"Users putting chained commands from a tool's README (install && verify) into a config field; setting env vars inline (VAR=x cmd); shell comments (#) or Windows line endings (\\r\\n) copied into apps.yaml or CLI config.","solutions":["Remove shell operators from the command: split chained commands and register only the single install command (e.g. 'npm install -g foo').","Set environment variables in your shell environment or the tool's config instead of inline VAR=value prefixes.","Strip trailing comments, trailing whitespace, and normalize line endings (no \\r) in the configured command string.","Install the tool manually (run the original command yourself) as the error message suggests."],"exampleFix":"// before\nparseCommand(\"npm install -g foo && foo --version\");\n// after\nparseCommand(\"npm install -g foo\");","handlingStrategy":"validation","validationCode":"const UNSAFE = /[&&|;><`$#\\n\\r]|\\$\\(|\\|\\|/;\nif (UNSAFE.test(cmd)) throw new Error(`Refusing unsafe install command: ${cmd}`);\nparseCommand(cmd);","typeGuard":"function isSafeCommand(cmd: string): boolean {\n  return !/[&&|;><`$#\\n\\r]|\\$\\(|\\|\\|/.test(cmd);\n}","tryCatchPattern":"try {\n  const { binary, args } = parseCommand(cmd);\n} catch (e) {\n  console.error(`Unsafe install command: ${cmd}. Install the tool manually.`);\n}","preventionTips":["Store only single, simple install commands in config (no && chaining).","Set env vars in your shell, not inline in the command string.","Trim and normalize line endings of config values on load.","Never paste multi-command snippets from READMEs into a single config field."],"tags":["security","command-injection","validation"],"backgroundTag":"unsafe-shell-operator","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}