T05 · Unauthorized Access and Privilege Escalation
Warning
- Location
- scripts/vet.py:98
- Finding
- Symbolic-Link Traversal Allows Reading Files Outside the Audited Skill## Vulnerability Details **File Location**: `scripts/vet.py:98-106` **Vulnerability Type**: Symbolic-link traversal and unauthorized local file access **Risk Level**: Medium ### Vulnerable Code ```python def _scan_files(self): for filepath in self.skill_path.rglob("*"): if filepath.is_dir(): continue if filepath.suffix in ('.pyc', '.pyo', '.so', '.dll', '.exe'): self.findings.append(Finding("danger", "binary", f"Binary file found: {filepath.name}", str(filepath), 0, 25)) continue try: content = filepath.read_text(encoding="utf-8", errors="ignore") except Exception: continue ``` ### Technical Analysis The scanner treats the audited Skill directory as untrusted input but does not reject symbolic links or verify that each resolved file remains inside `self.skill_path`. `Path.read_text()` follows a symbolic link when it points to a readable file. An attacker can therefore package a symbolic link whose apparent location is inside the Skill but whose target is an arbitrary file available to the user running the audit. The target's contents are then loaded and inspected by the scanner. This behavior violates least privilege because auditing a Skill only requires access to files physically contained within that Skill. The scanner should not follow references into unrelated portions of the local filesystem. ### Attack Path 1. An attacker creates a Skill directory containing a symbolic link, such as `linked-secret`, that points to a likely sensitive local path. 2. The victim obtains the untrusted Skill and runs `python3 scripts/vet.py --skill /path/to/skill`. 3. `rglob("*")` discovers the symbolic link as an entry in the audited directory. 4. The scanner does not reject the link or validate its resolved destination. 5. `filepath.read_text()` follows the link and reads the external file. 6. The external contents are ...[truncated 647 chars]
- Remediation
- ## Remediation Suggestions - Reject symbolic links before reading any entry: ```python if filepath.is_symlink(): self.findings.append(Finding( "warn", "symlink", "Symbolic links are not scanned", str(filepath.relative_to(self.skill_path)), 0, 0 )) continue ``` - Resolve the audit root once and confirm that every candidate's resolved path remains beneath it: ```python root = self.skill_path.resolve() try: resolved = filepath.resolve(strict=True) resolved.relative_to(root) except (FileNotFoundError, RuntimeError, ValueError): continue ``` - Perform containment validation immediately before opening the file to reduce time-of-check/time-of-use exposure. - Open files using directory-relative, no-follow operating-system primitives where supported, such as `os.open()` with `O_NOFOLLOW`. - Apply the same policy to file-size calculation and any future archive or dependency inspection logic.
