T09 · Insecure Skill Coding Practices
- Location
handler.js:12- Finding
Host-Shell Command Injection Through Unsafe tmux Command Construction
- Content
View full analysis
Vulnerability Details
File Location:
handler.js, lines 12–14
Vulnerability Type: OS command injection
Risk Level: HighVulnerable Code
js function sendCommand(cmd) { const escaped = cmd.replace(/"/g, '\\"'); execSync(`tmux send-keys -t claw "${escaped}" C-m`); }Technical Analysis
The
cmdvalue is supplied by the caller and interpolated into a command string executed byexecSync(). Node.js passes this string to a host shell. The implementation escapes only double-quote characters, which does not prevent shell evaluation of command substitutions such as$()or backticks.Consequently, a crafted value can cause the host shell to execute commands while constructing the arguments for
tmux. This execution occurs outside the designated tmux session and therefore violates the documented isolation expectation that commands are run inside sessionclaw.The
isDangerous()substring denylist does not eliminate this vulnerability. It only detects a small set of literal strings and does not account for shell substitutions, alternate command syntax, variable expansion, encoding, or equivalent utilities. A payload containing a substitution that does not include one of the blocked substrings can reachsendCommand()and execute in the host shell.Attack Path
- An attacker or untrusted caller invokes
claw_shell_runwith a craftedcommandcontaining shell substitution syntax, such as:sh echo $(touch /tmp/claw-shell-injection-proof) - The command passes the denylist because it contains none of the listed blocked substrings.
sendCommand()escapes only double quotes and interpolates the remaining input into:sh tmux send-keys -t claw "echo $(touch /tmp/claw-shell-injection-proof)" C-m- The host shell evaluates
$(touch /tmp/claw-shell-injection-proof)beforetmuxreceives its arguments. - The injected command executes direc ...[truncated 968 chars]
- An attacker or untrusted caller invokes
- Remediation
View remediation
Remediation Suggestions
Do not construct shell command strings with caller-controlled input. Invoke
tmuxdirectly with an argument array and disable shell interpretation. Send the command as literal text and submit Enter separately, for example:js const { execFileSync } = require("node:child_process"); function sendCommand(cmd) { execFileSync("tmux", ["send-keys", "-t", "claw", "-l", "--", cmd], { shell: false }); execFileSync("tmux", ["send-keys", "-t", "claw", "C-m"], { shell: false }); }Additional hardening should include:
- Avoid using shell-backed
execSync()for all fixed tmux operations; useexecFileSync()orspawnSync()with fixed executable names and argument arrays. - Treat substring denylisting as defense-in-depth only, not as an injection control.
- Implement explicit authorization state if dangerous commands are intended to run after user approval. The current API has no approval field and therefore cannot securely distinguish an approved request from an unapproved one.
- Apply an allowlist or a structured command policy if the tool does not require unrestricted shell access.
- Run the skill under a dedicated, least-privileged operating-system account with restricted filesystem, credential, and network access.
- Add regression tests using
$(), backticks, quotes, newlines, semicolons, pipes, redirections, and variable expansion to verify that input reaches tmux literally and is never evaluated by the host shell.
- Avoid using shell-backed
