T09 · Insecure Skill Coding Practices
Note
- Location
- scripts/package_skill.py:36
- Finding
- Archive Self-Inclusion When Output Is Placed Inside the Source Tree<![CDATA[ ## Vulnerability Details **File Location**: `scripts/package_skill.py:36-45` **Vulnerability Type**: Unsafe archive output-path handling **Risk Level**: Low ```python target_dir = (output_dir or skill_dir.parent).expanduser().resolve() target_dir.mkdir(parents=True, exist_ok=True) archive_path = target_dir / f"{skill_dir.name}.skill.zip" with ZipFile(archive_path, "w", ZIP_DEFLATED) as zf: for path in iter_files(skill_dir): zf.write(path, path.relative_to(skill_dir)) return archive_path ``` ### Technical Analysis The packaging function permits `output_dir` to resolve to the source Skill directory or one of its descendants. It opens the destination archive before recursively enumerating source files through `iter_files(skill_dir)`. If the destination archive is inside `skill_dir`, recursive enumeration can encounter the archive currently being written and attempt to add it to itself. The implementation also does not exclude pre-existing `*.skill.zip` archives. This can produce nondeterministic or corrupted packages and may unnecessarily increase resource consumption. Exploitation requires control over the packaging output path and write access to the selected directory. The issue does not provide command execution, privilege escalation, or access beyond the invoking process's existing filesystem permissions. ### Attack Path 1. Prepare or select a valid Skill directory that passes `validate_skill`. 2. Invoke the packaging script with an output directory located inside that Skill directory: ```bash python3 scripts/package_skill.py /path/to/skill /path/to/skill ``` 3. The script creates `/path/to/skill/skill.skill.zip`. 4. `iter_files()` recursively enumerates `/path/to/skill` after the archive has been opened. 5. The active archive can be treated as a source file and written into itself. Pre-existing package archives can likewise be included. 6. The resulting package may be corrupted, nondetermini ...[truncated 555 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Reject any archive destination located within the source Skill directory: ```python resolved_skill_dir = skill_dir.expanduser().resolve() target_dir = (output_dir or resolved_skill_dir.parent).expanduser().resolve() if target_dir == resolved_skill_dir or resolved_skill_dir in target_dir.parents: raise ValueError("Output directory must not be inside the skill directory.") ``` 2. Resolve `archive_path` and explicitly exclude it during file enumeration as defense in depth. 3. Exclude package artifacts such as `*.skill.zip` unless they are intentionally part of the Skill. 4. Enumerate and validate the complete input file list before opening the output archive. 5. Add regression tests covering: - Output equal to the Skill directory. - Output in a nested subdirectory. - A pre-existing archive in the source tree. - The normal default output in the Skill directory's parent. ]]>
