T09 · Insecure Skill Coding Practices
- Location
- index.js:112
- Finding
- Shell Command Injection Through Unquoted Workspace Paths<![CDATA[ ## Vulnerability Details **File Location**: `index.js`, lines 112–138 and 157–163 **Vulnerability Type**: Shell command injection through unquoted, configuration-derived paths **Risk Level**: Critical ### Vulnerable Code ```javascript const hrWorkspace = path.join(process.env.HOME || '/root', '.openclaw/hr_recruiter_workspace'); if (!fs.existsSync(hrWorkspace)) { console.log(`Creating HR workspace: ${hrWorkspace}`); execSync(`mkdir -p ${hrWorkspace}/skills`); } const templates = ['identity', 'soul', 'agents']; templates.forEach(t => { const src = path.join(skillSourcePath, 'assets/templates', `hr_${t}.md`); const dst = path.join(hrWorkspace, `${t.toUpperCase()}.md`); if (fs.existsSync(src)) { execSync(`cp ${src} ${dst}`); } }); const targetSkillPath = path.join(hrWorkspace, 'skills/feishu-team-manager'); execSync(`rm -rf ${targetSkillPath}`); execSync(`mkdir -p ${targetSkillPath}`); execSync(`cp -r ${skillSourcePath}/* ${targetSkillPath}/`); execSync(`openclaw agents add hr_recruiter --workspace ${hrWorkspace}`); ``` The same unsafe pattern occurs during synchronization: ```javascript const hrWorkspace = hrAgent.workspace; if (hrWorkspace && fs.existsSync(hrWorkspace)) { const targetSkillPath = path.join(hrWorkspace, 'skills/feishu-team-manager'); execSync(`cp -r ${skillSourcePath}/* ${targetSkillPath}/`); console.log("Skill files synchronized to the HR workspace."); } ``` ### Technical Analysis The code constructs shell command strings by directly interpolating paths derived from: - The `HOME` environment variable. - The Skill installation path. - The `workspace` property in `openclaw.json`. No shell quoting, escaping, canonicalization, or trust-boundary validation is applied. Node.js `execSync()` invokes a shell, so metacharacters embedded in one of these values are interpreted as shell syntax rather than as part of a filesystem path. The synchronization branch is particularly dangerous because ...[truncated 1293 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Do not invoke a shell for filesystem operations. Replace the commands with native Node.js APIs: - `fs.mkdirSync(path, { recursive: true })` - `fs.cpSync(source, destination, { recursive: true })` - `fs.rmSync(path, { recursive: true, force: true })` 2. If an external command is unavoidable, use `execFileSync()` or `spawnSync()` with an argument array and `shell: false`. 3. Resolve each path with `fs.realpathSync()` or `path.resolve()` and verify that it remains beneath an explicitly approved OpenClaw workspace root. 4. Reject workspace values containing null bytes, control characters, or unexpected path structures. 5. Before any recursive deletion, verify that the target: - Is not empty. - Is not `/` or the user's home directory. - Is a child of the expected Skill directory. 6. Treat values loaded from `openclaw.json` as untrusted configuration rather than safe shell input. ]]>
