T05 · Unauthorized Access and Privilege Escalation
- Location
scripts/vet_skill.py:241- Finding
Symbolic Links Can Cause Reads Outside the Intended Skill Directory
- Content
View full analysis
Vulnerability Details
File Location:
scripts/vet_skill.py:241-256
Vulnerability Type: Insufficient filesystem boundary enforcement
Risk Level: MediumComplete Code Snippet
python def find_files(skill_dir: Path) -> List[Path]: """Find all relevant files in skill directory""" files = [] extensions = {'.py', '.sh', '.bash', '.zsh', '.js', '.ts', '.md', '.json', '.yaml', '.yml'} for ext in extensions: files.extend(skill_dir.rglob(f'*{ext}')) for f in skill_dir.rglob('*'): if f.is_file() and f.suffix == '': try: content = f.read_text(encoding='utf-8', errors='ignore')[:100] if content.startswith('#!'): files.append(f) except: pass return sorted(set(files))Discovered files are subsequently read without validating their resolved locations:
python content = file_path.read_text(encoding='utf-8', errors='ignore')Technical Analysis
The scanner recursively discovers paths beneath an untrusted Skill directory and reads them directly. It does not reject symbolic links or verify that each canonical, resolved target remains within the canonical Skill root.
A symbolic link may therefore appear to reside inside the Skill while its target resides anywhere accessible to the account running the scanner.
Path.read_text()follows such links. The same behavior exists both during extensionless-script detection and during subsequent security-pattern scanning.The issue violates least-privilege expectations: a Skill vetting utility only needs to read regular files physically contained in the submitted Skill directory. It does not need permission to follow attacker-selected links into unrelated user or system locations.
Attack Path
- An attacker creates a Skill containing a supported path such as `references/loca ...[truncated 1431 chars]
- Remediation
View remediation
Remediation Suggestions
-
Resolve the Skill root once and verify every candidate's canonical path before reading:
python root = skill_dir.resolve(strict=True) for candidate in skill_dir.rglob('*'): if candidate.is_symlink(): continue resolved = candidate.resolve(strict=True) if not resolved.is_relative_to(root): continue -
Reject symbolic links explicitly rather than relying only on canonical-path checks.
-
Apply the same validation in every function that opens a discovered path, creating a defense-in-depth boundary at the point of use.
-
Require candidates to be regular files.
-
Add maximum file-size and total-scan-size limits before reading content.
-
Catch filesystem race conditions and revalidate paths as close as possible to opening them.
-
Run scans under a minimally privileged account or sandbox that cannot read unrelated credentials and private files.
-
Add regression tests using links to files inside and outside the Skill root.
-
