T09 · Insecure Skill Coding Practices
- Location
cli.js:31- Finding
OS Command Injection Through Unsafely Constructed Shell Command
- Content
View full analysis
Vulnerability Details
File Location:
cli.js, lines 31–45
Vulnerability Type: OS command injection
Risk Level: HighVulnerable Code
js // Pass command line arguments to the script const args = process.argv.slice(2); try { // Set environment variables for the script const env = { ...process.env, LC_ALL: 'en_US.UTF-8', // Ensure English locale for number formatting }; // Run script and capture output const output = execSync(`"${scriptPath}" ${args.map(arg => `"${arg}"`).join(' ')}`, { env, stdio: 'pipe', encoding: 'utf-8' });Technical Analysis
The CLI incorporates every user-controlled command-line argument into a command string passed to Node.js
execSync(). String-formexecSync()invokes a shell, so shell syntax in the resulting command is interpreted rather than passed directly tocost_report.sh.Wrapping each argument in double quotes is not sufficient escaping. An argument can contain a double quote that terminates the intended quoted context, followed by shell control operators and an arbitrary command. Shell substitutions such as
$(...)also remain active inside double-quoted shell strings.The shell script's own option validation does not mitigate the issue because the injected command is interpreted by the shell before
cost_report.shreceives and validates its arguments.Attack Path
-
An attacker gains influence over arguments supplied to
cli.js, such as through a wrapper, automation job, agent-generated command, or a command copied and executed by a user. -
The attacker provides an argument containing shell syntax. For example, the following demonstrates arbitrary command execution by creating a file:
bash node cli.js --format '"; touch /tmp/openclaw-cli-injected; #' -
cli.jsconstructs a command resembling:bash "/path/to/scripts/cost_report.sh" "--format" ""; touch /tmp/openclaw-cli-injected; #" -
execSync()passes thi ...[truncated 983 chars]
-
- Remediation
View remediation
Remediation Suggestions
Replace string-based
execSync()with an API that passes arguments directly without invoking a shell, such asexecFileSync():js const { execFileSync } = require('child_process'); const output = execFileSync(scriptPath, args, { env, stdio: 'pipe', encoding: 'utf-8', shell: false });Apply the following additional hardening measures:
- Validate arguments against the script's supported options before execution.
- Restrict
--formatto an explicit allowlist such astext,json, anddiscord. - Reject missing values for options that require an argument.
- Do not attempt to solve the issue with custom shell escaping; avoid shell interpretation entirely.
- Add regression tests using arguments containing quotes, semicolons, command substitutions, backticks, newlines, and redirection operators.
- Prefer setting executable permissions during packaging or installation rather than invoking
chmodon every CLI run.
