T09 · Insecure Skill Coding Practices
- Location
scripts/editorial.js:25- Finding
Shell Command Injection Through Git Commit Messages
- Content
View full analysis
Vulnerability Details
File Location:
scripts/editorial.js, lines 25-31; untrusted data reaches the vulnerable function from lines 94-116, 118-131, and 133-180
Vulnerability Type: OS command injection
Risk Level: HighVulnerable Code
js function gitCommit(message) { try { execSync(`git -C ${EDITORIAL_DIR} add .`, { stdio: 'ignore' }); execSync(`git -C ${EDITORIAL_DIR} commit -m "${message}"`, { stdio: 'ignore' }); } catch (e) { // Ignore commit failures (no changes, not in git repo, etc.) } }Attacker-controlled command arguments and environment values are incorporated into
message, for example:js const agent = process.env.OPENCLAW_AGENT || process.env.USER || 'unknown'; // ... gitCommit(`editorial: ${agent} claimed ${contentId} for ${action} on ${channel}`);Equivalent untrusted interpolation also occurs when releasing and publishing content.
Technical Analysis
execSync()receives a single command string, so Node.js executes it through a shell. The commit message contains values derived from CLI arguments—contentId,action, andchannel—and fromOPENCLAW_AGENTorUSER. None of these values are validated or safely escaped.Wrapping the message in double quotes does not neutralize shell syntax. POSIX shells still evaluate command substitutions such as
$(command)and backticks inside double-quoted text. An attacker can therefore cause commands to run beforegit commitis invoked.The surrounding
try/catchdoes not mitigate the vulnerability. Shell expansion occurs before Git processes its arguments, and any injected command may already have completed even if the subsequent Git operation fails.Attack Path
- An attacker gains the ability to invoke a mutating CLI command or influence its arguments or environment.
- The attacker supplies shell substitution syntax in
content-id,action,channel, orOPENCLAW_AGENT, such as a value containing$(attacker_command).
...[truncated 944 chars]
- Remediation
View remediation
Remediation Suggestions
Avoid invoking Git through a shell. Use
execFileSync()orspawnSync()with an explicit argument array:js const { execFileSync } = require('child_process'); function gitCommit(message) { try { execFileSync('git', ['-C', EDITORIAL_DIR, 'add', '.'], { stdio: 'ignore' }); execFileSync('git', ['-C', EDITORIAL_DIR, 'commit', '-m', message], { stdio: 'ignore' }); } catch (error) { // Log or handle the failure appropriately. } }Additionally:
- Validate
contentId,action,channel, and agent identity using strict allowlists and length limits. - Do not treat shell escaping as the primary fix; eliminating shell parsing is safer.
- Distinguish expected Git outcomes, such as “nothing to commit,” from unexpected failures rather than silently suppressing every error.
- Add regression tests containing
$(), backticks, quotes, semicolons, newlines, and option-like values. - Use a controlled identity value rather than falling back to arbitrary environment data where feasible.
- Validate
