T09 · Insecure Skill Coding Practices
- Location
scripts/package_skill.py:70- Finding
Symbolic Links Can Include Files Outside the Skill Directory in Packaged Archives
- Content
View full analysis
Vulnerability Details
File Location:
scripts/package_skill.py, lines 70–75
Vulnerability Type: Unrestricted symbolic-link traversal during archive creation
Risk Level: MediumVulnerable Code
python # 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 processes every file under the supplied Skill directory.
Path.is_file()follows symbolic links, whilezipfile.ZipFile.write()reads the content of the link target.The archive name is calculated from the symbolic link's lexical location rather than from the resolved target. Consequently, a symbolic link that appears to be inside the Skill can point to a file outside it, and the external file's contents will be stored in the generated archive under an innocuous internal path.
The validator does not inspect packaged resources for symbolic links or verify that their resolved paths remain within the Skill root.
Attack Path
- An attacker creates or distributes a Skill directory containing a symbolic link, such as
assets/host-data, that points to a sensitive local file. - A user invokes
scripts/package_skill.pyon that directory. - Recursive traversal discovers the symbolic link.
is_file()reports that the link target is a file.zipf.write()follows the link and reads the external target.- The external file is embedded in the generated
.skillarchive. - If the archive is shared or published, the captured local information is disclosed.
Impact Assessment
The vulnerability allows disclosure of files readable by the account running the packaging script. Depending on the host environment, exposed data could inclu ...[truncated 354 chars]
- An attacker creates or distributes a Skill directory containing a symbolic link, such as
- Remediation
View remediation
Remediation Suggestions
-
Reject symbolic links explicitly before adding files to the archive:
python if file_path.is_symlink(): raise ValueError(f"Symbolic links are not permitted: {file_path}") -
Resolve every candidate path and verify that it remains beneath the resolved Skill root:
python skill_root = skill_path.resolve() for file_path in skill_root.rglob("*"): if file_path.is_symlink(): raise ValueError(f"Symbolic links are not permitted: {file_path}") resolved_file = file_path.resolve(strict=True) if not resolved_file.is_relative_to(skill_root): raise ValueError(f"File escapes the skill directory: {file_path}") if resolved_file.is_file(): arcname = resolved_file.relative_to(skill_root.parent) zipf.write(resolved_file, arcname) -
Apply the same containment validation to
SKILL.mdand all resources before packaging. -
Consider using an explicit resource allowlist and excluding sensitive filenames such as environment files, private keys, and credential stores.
-
Add automated tests covering file symlinks, directory symlinks, broken links, and links targeting files outside the Skill root.
-
