T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/inspect.py:155
- Finding
- Remote Command Injection Through Invalid Service Names<![CDATA[ ## Vulnerability Details **File Location**: `scripts/inspect.py:155-161` and execution sink at `scripts/inspect.py:248-252`; equivalent implementation in `scripts/inspect.mjs:142-149` and execution sink at `scripts/inspect.mjs:357-360` **Vulnerability Type**: OS command injection **Risk Level**: High ### Vulnerable Code Python implementation: ```python if not re.match(r'^[a-zA-Z0-9_-]+$', name): safe_name = re.sub(r'[^a-zA-Z0-9_-]', '_', name) out.append({ "id": f"svc_{safe_name}_invalid", "cmd": f"echo 'Invalid service name (only alphanumeric, hyphens, underscores allowed): {name}'", "timeoutSec": 3, }) continue ``` The generated command is subsequently passed to a remote shell: ```python remote = f"bash -lc '{cmd.replace(chr(39), chr(39) + '\"' + chr(39) + chr(39))}'" full_cmd = ssh_base + [dest, remote] try: result = subprocess.run( full_cmd, ``` Equivalent Node.js implementation: ```javascript if (!/^[a-zA-Z0-9_-]+$/.test(name)) { out.push({ id: `svc_${name.replace(/[^a-zA-Z0-9_-]/g, '_')}_invalid`, cmd: `echo 'Invalid service name (only alphanumeric, hyphens, underscores allowed): ${name}'`, timeoutSec: 3, }); continue; } ``` The command reaches this execution sink: ```javascript for (const c of group.commands) { const timeoutMs = Number(c.timeoutSec ?? 10) * 1000; const remote = `bash -lc ${shellQuote(c.cmd)}`; const { error, stdout, stderr } = await execFileP('ssh', [...sshBase, dest, remote], { timeoutMs }); ``` ### Technical Analysis The application correctly recognizes that a service name fails the allowlist expression, but it then embeds that invalid value directly inside a single-quoted shell command. The input is not safely encoded before becoming part of the command string. A service name containing a single quote can terminate the intended `echo` argument and append additional shell syntax. For example, a configuration value conceptually shaped like ...[truncated 2114 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Do not construct or execute any remote command for an invalid service name. Record the validation failure locally in the report and skip SSH execution. 2. Treat the service-name allowlist as a hard rejection boundary: ```python if not re.fullmatch(r"[A-Za-z0-9_-]+", name): out.append({ "id": f"invalid_service_{safe_name}", "validation_error": "Invalid service name", "timeoutSec": 0, }) continue ``` 3. Apply the same hard-rejection behavior in the Node.js implementation. 4. Avoid `bash -lc` where possible. Use a fixed remote helper with data passed as positional arguments rather than interpolating configuration values into shell source. 5. If a shell cannot be avoided, use a well-tested shell-quoting routine for every variable and never reuse rejected input in executable text. 6. Validate the complete target configuration before opening an SSH connection. Reject invalid host, port, user, key path, service, timeout, and command fields. 7. Protect `references/targets.yaml` and `references/checks.yaml` with restrictive local permissions and trusted deployment controls. 8. Add regression tests using service names containing single quotes, semicolons, command substitutions, newlines, redirections, and shell operators. Tests should verify that no SSH command is launched for invalid input. 9. Configure the remote inspection account with least privilege, no interactive shell where practical, no passwordless unrestricted `sudo`, and an SSH `authorized_keys` forced command that permits only approved inspection operations. ]]>
