T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/run.py:61
- Finding
- Arbitrary Python File Execution Through Path Traversal<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run.py:61-89`; `scripts/setup_environment.py:122-139` **Vulnerability Type**: Unrestricted script path traversal and arbitrary local Python execution **Risk Level**: High ### Vulnerable Code `scripts/run.py:61-89`: ```python script_name = sys.argv[1] script_args = sys.argv[2:] # Handle both "scripts/script.py" and "script.py" formats if script_name.startswith("scripts/"): # Remove the scripts/ prefix if provided script_name = script_name[8:] # len('scripts/') = 8 # Ensure .py extension if not script_name.endswith(".py"): script_name += ".py" # Get script path skill_dir = Path(__file__).parent.parent script_path = skill_dir / "scripts" / script_name if not script_path.exists(): print(f"❌ Script not found: {script_name}") print(f" Working directory: {Path.cwd()}") print(f" Skill directory: {skill_dir}") print(f" Looked for: {script_path}") sys.exit(1) # Ensure venv exists and get Python executable venv_python = ensure_venv() # Build command cmd = [str(venv_python), str(script_path)] + script_args # Run the script try: result = subprocess.run(cmd) ``` `scripts/setup_environment.py:122-139`: ```python def run_script(self, script_name: str, args: list = None) -> int: """Run a script with the virtual environment""" script_path = self.skill_dir / "scripts" / script_name if not script_path.exists(): print(f"❌ Script not found: {script_path}") return 1 # Ensure venv is set up if not self.ensure_venv(): print("❌ Failed to set up environment") return 1 # Build command cmd = [str(self.venv_python), str(script_path)] if args: cmd.extend(args) print(f"🚀 Running: {script_name} with venv Python") try: # Run the script with venv Python result = subprocess.run(cmd) ``` ### Technical Analysis Both entry points accept a caller-controlled script name and append it ...[truncated 1691 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Replace free-form script selection with a strict allowlist: ```python ALLOWED_SCRIPTS = { "auth_manager.py": scripts_dir / "auth_manager.py", "publisher.py": scripts_dir / "publisher.py", } ``` 2. Reject absolute paths, path separators, `..` components, and unexpected extensions before resolving the path. 3. Canonicalize both paths and verify containment: ```python scripts_dir = (skill_dir / "scripts").resolve() candidate = (scripts_dir / script_name).resolve(strict=True) if candidate.parent != scripts_dir: raise ValueError("Script must be a direct child of the scripts directory") ``` 4. Reject symbolic links unless they are explicitly required and their final targets remain inside the trusted directory. 5. Apply the same validation in both `run.py` and `SkillEnvironment.run_script`. 6. Add tests covering absolute paths, `../` traversal, nested traversal, symbolic-link escapes, and valid allowlisted names. ]]>
