jackwener/OpenCLI · error
Install command contains unsafe shell operators and cannot b
Error message
Install command contains unsafe shell operators and cannot be executed securely: "${cmd}". Please install the tool manually. What it means
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.
Source
Thrown at src/external.ts:115
export function formatExternalCliLabel(cli: ExternalCliConfig): string {
return cli.package && cli.package !== cli.name ? `${cli.name}(${cli.package})` : cli.name;
}
/**
* Safely parses a command string into a binary and argument list.
* Rejects commands containing shell operators (&&, ||, |, ;, >, <, `) that
* cannot be safely expressed as execFileSync arguments.
*
* Args:
* cmd: Raw command string from YAML config (e.g. "brew install gh")
*
* Returns:
* Object with `binary` and `args` fields, or throws on unsafe input.
*/
export function parseCommand(cmd: string): { binary: string; args: string[] } {
const shellOperators = /&&|\|\|?|;|[><`$#\n\r]|\$\(/;
if (shellOperators.test(cmd)) {
throw new Error(
`Install command contains unsafe shell operators and cannot be executed securely: "${cmd}". ` +
`Please install the tool manually.`
);
}
// Tokenise respecting single- and double-quoted segments (no variable expansion).
const tokens: string[] = [];
const re = /(?:"([^"]*)")|(?:'([^']*)')|(\S+)/g;
let match: RegExpExecArray | null;
while ((match = re.exec(cmd)) !== null) {
tokens.push(match[1] ?? match[2] ?? match[3]);
}
if (tokens.length === 0) {
throw new Error(`Install command is empty.`);
}
const [binary, ...args] = tokens;View on GitHub (pinned to 49907e53dc)
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.
Example fix
// before
parseCommand("npm install -g foo && foo --version");
// after
parseCommand("npm install -g foo"); Defensive patterns
Strategy: validation
Validate before calling
const UNSAFE = /[&&|;><`$#\n\r]|\$\(|\|\|/;
if (UNSAFE.test(cmd)) throw new Error(`Refusing unsafe install command: ${cmd}`);
parseCommand(cmd); Type guard
function isSafeCommand(cmd: string): boolean {
return !/[&&|;><`$#\n\r]|\$\(|\|\|/.test(cmd);
} Try / catch
try {
const { binary, args } = parseCommand(cmd);
} catch (e) {
console.error(`Unsafe install command: ${cmd}. Install the tool manually.`);
} Prevention
- 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.
When it happens
Trigger: 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'.
Common situations: 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.
Related errors
- nvd CVE id is required (e.g. "CVE-2021-44228")
- Unexpected Xiaohongshu search harvest row ${index + 1} URL;
- Youdao Note URL must use http or https
- INVALID_ARGUMENT
- INVALID_ARGUMENT
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/53617284ced403f3.
Report an issue: GitHub.