T09 · Insecure Skill Coding Practices
Error
- Location
- server.js:163
- Finding
- Unauthenticated Remote Shell Command Injection<![CDATA[ ## Vulnerability Details **File Location**: `server.js:163-200` and `server-gemini.js:93-115` **Vulnerability Type**: OS command injection through shell interpolation **Risk Level**: Critical ### Vulnerable Code ```javascript app.post('/api/agents/:id/chat', async (req, res) => { try { const { id } = req.params; const { message } = req.body; if (!message) { return res.status(400).json({ success: false, error: 'Message is required' }); } const agentConfigPath = path.join(CONFIG.agentsDir, id, 'config.json'); let agentConfig = null; try { agentConfig = await readJsonFile(agentConfigPath); } catch (e) {} const agentId = id.replace('agent-', ''); const workspace = agentConfig?.workspace || `workspace-${agentId}`; const workspacePath = path.join(CONFIG.workspaceDir, workspace); try { await fs.access(workspacePath); } catch (e) { return res.status(404).json({ success: false, error: `Agent workspace does not exist: ${workspacePath}` }); } const safeMessage = message.replace(/"/g, '\\"').replace(/\n/g, ' '); const { stdout } = await execCmd( `cd "${workspacePath}" && openclaw agent --agent ${agentId} --message "${safeMessage}" --json 2>&1` ); ``` The command execution helper explicitly enables a shell: ```javascript async function execCmd(cmd) { return new Promise((resolve, reject) => { exec(cmd, { shell: 'zsh' }, (error, stdout, stderr) => { if (error) reject(error); else resolve({ stdout, stderr }); }); }); } ``` ### Technical Analysis The request-controlled `message` is interpolated into a command executed by `zsh`. The attempted sanitization only escapes double quotes and replaces newline characters. It does not prevent shell expansion within double quotes, including: - Command substitution using `$(...)` - Command substitution using backticks - Parameter expansion - Other shell-specific substitutio ...[truncated 1448 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Replace `child_process.exec()` with `execFile()` or `spawn()` using an argument array and `shell: false`: ```javascript const { execFile } = require('child_process'); execFile( 'openclaw', ['agent', '--agent', agentId, '--message', message, '--json'], { cwd: workspacePath }, callback ); ``` 2. Do not use `cd`, redirection, or other shell syntax. Use the child-process `cwd` option. 3. Validate Agent IDs with a strict allowlist such as `^agent-[a-z0-9-]+$`. 4. Add authentication and authorization to the chat endpoint. 5. Bind the service to `127.0.0.1` by default and explicitly allow only trusted CORS origins. 6. Apply request-size and execution-time limits, and run OpenClaw under a restricted operating-system account. ]]>
