T09 · Insecure Skill Coding Practices
- Location
scripts/utils.py:233- Finding
Shell Command Injection Through Untrusted Skill Names
- Content
View full analysis
str: """Run a shell command and return stdout.""" try: result = subprocess.run( ["/bin/sh", "-c", cmd], capture_output=True, text=True, timeout=timeout ) return result.stdout.strip() ``` ```python # scripts/scanner.py:41-46 # Call the ClawHub API try: result = run_cmd(f"npx clawhub search \"{query}\" --limit {limit}", timeout=30) skills = [] if result: for line in result.strip().split("\n"): ``` ```python # scripts/scanner.py:215 inspect_output = run_cmd(f"npx clawhub inspect {skill_name} 2>&1", timeout=15) ``` ```python # scripts/checks.py:172-176 def inspect_clawhub_version(skill_name: str) -> str | None: """Obtain the latest Skill version through clawhub inspect.""" output = run_cmd( f"npx clawhub inspect {skill_name} 2>/dev/null", timeout=15 ) ``` ### Technical Analysis The common `run_cmd` helper executes every supplied command through `/bin/sh -c`. Several callers construct command strings by interpolating `skill_name` without quoting, validation, or argument separation. In the recommendation flow, Skill names are parsed from ClawHub search output using `^(\S+)`, which permits shell metacharacters such as semicolons, pipes, command substitutions, and redirection operators. These remotely supplied names are subsequently passed to `security_scan_skill`, where they are interpolated directly into a shell command. The version-check path has the same defect for names parsed from `npx clawhub list`. The fixed recommendation queries currently limit exploitation through `query`, but the shared ...[truncated 1426 chars]- Remediation
View remediation
str: result = subprocess.run( args, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, timeout=timeout, check=False, shell=False, ) return result.stdout.strip() ``` 2. Invoke ClawHub with separated arguments: ```python run_cmd(["npx", "--no-install", "clawhub", "inspect", skill_name], timeout=15) run_cmd(["npx", "--no-install", "clawhub", "search", query, "--limit", str(limit)]) ``` 3. Validate every Skill name before use: ```python if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._-]*", skill_name): raise ValueError("Invalid Skill name") ``` 4. Require `limit` to be an integer within a small fixed range. 5. Replace shell redirections with `stderr=subprocess.DEVNULL`. 6. Treat registry and CLI output as untrusted, even when produced by an expected executable. 7. Add regression tests covering semicolons, pipes, backticks, `$()`, newlines, and redirection characters. ]]>
