T09 · Insecure Skill Coding Practices
- Location
- src/openclaw/openclaw-invoker.ts:297
- Finding
- Shell Command Injection Through Model Messages<![CDATA[ ## Vulnerability Details **File Location**: `src/openclaw/openclaw-invoker.ts:297-320` **Vulnerability Type**: OS command injection **Risk Level**: Critical ### Vulnerable Code ```ts const messagesJson = JSON.stringify(request.messages); const modelArg = request.model; // Build the base command let command = `${this.config.openclawPath} `; // Attempt to use the models subcommand command += `models invoke --model "${modelArg}" --messages '${messagesJson}'`; // Add optional parameters if (request.maxTokens) { command += ` --max-tokens ${request.maxTokens}`; } if (request.temperature !== undefined) { command += ` --temperature ${request.temperature}`; } logger.debug('Executing CLI command', { requestId, command: command.substring(0, 100) + '...', }); // Execute the command const { stdout, stderr } = await execAsync(command, { timeout: this.config.timeoutMs, cwd: this.config.workspaceDir, }); ``` ### Technical Analysis The invoker serializes caller-controlled messages and interpolates them directly into a shell command surrounded by single quotes. A single quote inside message content can terminate the `--messages` argument and introduce additional shell operators and commands. The model argument and mutable `openclawPath` configuration are also interpolated into the command string. Request validation verifies message types and whether the model appears in the provider model list, but it does not perform shell escaping. Because Node.js `child_process.exec()` invokes a shell, shell metacharacters are interpreted rather than passed literally to the OpenClaw executable. ### Attack Path 1. An attacker supplies a model request containing crafted message content, such as content that closes the single-quoted `--messages` argument. 2. `invokeModel()` validates that the content is a string but does not reject shell syntax. 3. For a DeepSeek provider, `selectInvocationStrategy()` selects `cli-direct`. 4. `invokeViaCliDirect()` inserts the s ...[truncated 704 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Replace `child_process.exec()` with `execFile()` or `spawn()` using an argument array. - Pass the model, message JSON, token count, and temperature as separate arguments without invoking a shell. - Prefer transmitting request JSON through the child process's standard input rather than a command-line argument. - Resolve and allowlist the OpenClaw executable path; do not permit arbitrary runtime modification of `openclawPath`. - Retain strict model allowlisting and add numeric range validation for token and temperature values. - Add regression tests containing single quotes, command substitutions, semicolons, newlines, backticks, and shell redirection syntax. ]]>
