T09 · Insecure Skill Coding Practices
- Location
scripts/openclaw-tools.js:43- Finding
OS Command Injection in the OpenClaw CLI Wrapper
- Content
View full analysis
Vulnerability Details
File Location:
scripts/openclaw-tools.js:43-62
Vulnerability Type: Shell command injection through unsafe string interpolation
Risk Level: HighVulnerable Code
js const paramString = Object.entries(params) .map(([key, value]) => { if (typeof value === 'boolean') { return value ? `--${key}` : ''; } else if (value !== undefined && value !== null) { return `--${key} "${String(value).replace(/"/g, '\\"')}"`; } return ''; }) .filter(Boolean) .join(' '); const command = `openclaw tool ${toolName} ${paramString}`; console.log(`[CLI模式] 执行: ${command}`); const output = execSync(command, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], cwd: WORKSPACE });Technical Analysis
The exported
callTool()function constructs a shell command by directly interpolatingtoolName, parameter keys, and parameter values into a string passed tochild_process.execSync().The implementation only escapes double-quote characters in values. This does not prevent shell evaluation inside double quotes. Shell substitutions such as
$(command)and backtick substitutions remain active. Parameter names andtoolNamereceive no escaping or validation at all, allowing additional shell metacharacters to be introduced through those fields.Because
execSync()executes the resulting string through a shell, attacker-controlled data can alter the intended command rather than remaining a literal OpenClaw argument. The issue is reachable through the exportedcallTool()API and through higher-level exported functions that forward values to it, such asgetGoalSuggestion(context)andupdateContext(key, value).Attack Path
- The OpenClaw executable is present, causing
isOpenClawAvailable()to select CLI mode rather than the filesystem fallback. - An integration, plugin, or other caller passes attacker-controlled text to an exported wrapper function or directly to
callTool().
...[truncated 1366 chars]
- The OpenClaw executable is present, causing
- Remediation
View remediation
Remediation Suggestions
- Replace shell-based execution with an argument-array API:
js import { execFileSync } from 'node:child_process'; const allowedTools = new Set([ 'signal_publish', 'signal_query', 'signal_resolve', 'context_get', 'context_update', 'goal_suggest' ]); if (!allowedTools.has(toolName)) { throw new Error('Unsupported tool name'); } const args = ['tool', toolName]; for (const [key, value] of Object.entries(params)) { if (!/^[A-Za-z0-9_-]+$/.test(key)) { throw new Error(`Invalid parameter name: ${key}`); } if (typeof value === 'boolean') { if (value) args.push(`--${key}`); } else if (value !== undefined && value !== null) { args.push(`--${key}`, String(value)); } } const output = execFileSync('openclaw', args, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], cwd: WORKSPACE, shell: false });- Allowlist supported tool names and parameter names rather than accepting arbitrary identifiers.
- Validate value types and enforce reasonable length limits before invoking the CLI.
- Avoid logging complete generated commands when arguments may contain private context or signal data.
- Add regression tests using values containing
$(...), backticks, semicolons, newlines, quotes, and leading option characters. Tests should confirm that these values are passed literally and cannot create side effects. - Prefer a direct OpenClaw library or structured API over command-line invocation when one is available.
