T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/operate_ui.py:315
- Finding
- Shell Command Injection Through the Wait Refresh Command## Vulnerability Details **File Location**: `scripts/operate_ui.py:315-322` **Vulnerability Type**: Arbitrary shell command execution through unvalidated CLI input **Risk Level**: High ### Vulnerable Code ```python def run_refresh_command(cmd: str, timeout: float, ignore_errors: bool) -> None: result = subprocess.run( cmd, shell=True, text=True, capture_output=True, timeout=timeout, ) if result.returncode == 0: return ``` The command originates from the command-line option defined at `scripts/operate_ui.py:764-768`: ```python wait_p.add_argument( "--refresh-cmd", default=None, help="Optional shell command run before each poll (e.g. re-parse screenshot).", ) ``` ### Technical Analysis The value supplied through `--refresh-cmd` is passed directly to `subprocess.run()` with `shell=True`. The operating-system shell therefore interprets metacharacters, substitutions, pipelines, redirections, and command separators contained in the value. No command allowlist, argument separation, escaping, or validation is applied. Although this option is exposed as a command-line feature, it becomes a command-injection boundary when an AI Agent constructs it from untrusted task instructions, document content, OCR output, UI text, or another externally influenced source. The command is executed before every polling attempt in the `wait` workflow, so a malicious command may also run repeatedly until the wait condition succeeds or times out. ### Attack Path 1. An attacker places a malicious instruction in content that the Agent processes, such as UI text, a document, or task-supplied parameters. 2. The content persuades or causes the Agent to pass an attacker-controlled string to `operate_ui.py wait --refresh-cmd`. 3. The `wait` command passes that string to `run_refresh_command()`. 4. `subprocess.run()` invokes the string through the system shell because `shell=True` is enabled. 5. Shell meta ...[truncated 1048 chars]
- Remediation
- ## Remediation Suggestions 1. Remove `shell=True` and execute commands as an explicit argument array: ```python subprocess.run( command_arguments, shell=False, text=True, capture_output=True, timeout=timeout, check=False, ) ``` 2. Prefer implementing screenshot refresh and parsing directly in Python rather than accepting a generic command. 3. If configurable refresh behavior is required, expose separate typed options for the executable, image path, output path, and other supported arguments. 4. Restrict executable selection to a small allowlist of reviewed local programs. 5. Reject shell metacharacters and command-substitution syntax as defense in depth, but do not treat filtering as a substitute for removing the shell. 6. Require explicit user confirmation before executing any externally supplied refresh operation. 7. Run refresh operations in a restricted environment with minimal filesystem and network access. 8. Add tests verifying that values containing separators, substitutions, redirects, and pipelines cannot cause additional commands to run.
