T09 · Insecure Skill Coding Practices
- Location
scripts/package_skill.py:74- Finding
Arbitrary Local File Disclosure Through Symlink Following During Skill Packaging
- Content
View full analysis
Vulnerability Details
File Location:
scripts/package_skill.py, lines 74-83
Vulnerability Type: Symlink-based local file disclosure
Risk Level: HighVulnerable Code
python try: with zipfile.ZipFile(skill_filename, "w", zipfile.ZIP_DEFLATED) as zipf: # Walk through the skill directory for file_path in skill_path.rglob("*"): if file_path.is_file(): # Calculate the relative path within the zip arcname = file_path.relative_to(skill_path.parent) zipf.write(file_path, arcname) print(f" Added: {arcname}")Technical Analysis
The packaging routine recursively enumerates files under a user-selected Skill directory and adds every path for which
Path.is_file()returns true. It does not reject symbolic links or verify that each file's resolved target remains insideskill_path.For a symbolic link to a regular file, both
Path.is_file()andzipfile.ZipFile.write()follow the link. Therefore, a Skill directory can contain a link whose apparent archive path is inside the Skill while its target is an arbitrary readable file elsewhere on the host.The validation performed before packaging does not mitigate this issue.
validate_skill()checks theSKILL.mdfrontmatter but does not inspect package contents, reject symbolic links, or enforce resolved-path containment.Attack Path
- An attacker prepares a Skill directory with a valid
SKILL.md. - The attacker adds a file symlink inside the Skill, such as
assets/secret, pointing to a sensitive host file such as/home/user/.ssh/id_rsaor a project credential file. - The victim runs
scripts/package_skill.pyagainst the attacker-controlled Skill directory. skill_path.rglob("*")discovers the symlink.file_path.is_file()follows the symlink and accepts it because its target is a regular file.- `zipf.write(file_path, ar ...[truncated 882 chars]
- An attacker prepares a Skill directory with a valid
- Remediation
View remediation
Remediation Suggestions
-
Reject symbolic links before packaging:
python if file_path.is_symlink(): raise ValueError(f"Symbolic links are not permitted: {file_path}") -
Resolve and validate every candidate path before reading it:
python root = skill_path.resolve() for file_path in skill_path.rglob("*"): if file_path.is_symlink(): raise ValueError(f"Symbolic links are not permitted: {file_path}") resolved = file_path.resolve(strict=True) if not resolved.is_relative_to(root): raise ValueError(f"Path escapes skill directory: {file_path}") if resolved.is_file(): arcname = file_path.relative_to(skill_path.parent) zipf.write(resolved, arcname) -
Perform the same symlink and containment checks during validation so unsafe packages fail before archive creation.
-
Avoid placing the output archive inside the source Skill directory. Explicitly reject an output path contained by
skill_pathto prevent recursive or unintended archive inclusion. -
Where race-resistant handling is required, open files using operating-system primitives that prohibit symlink following, such as
O_NOFOLLOWon supported platforms, and archive from the verified file descriptor. -
Add regression tests covering:
- Symlinks to files outside the Skill directory
- Symlinks to files inside the Skill directory
- Broken symlinks
- Directory symlinks
- Output archives placed inside the source directory
- Paths changed between validation and packaging
-
