T05 · Unauthorized Access and Privilege Escalation
- Location
scripts/skillguard.py:348- Finding
Scanner Follows Symlinks Outside the Audited Skill Directory
- Content
View full analysis
Vulnerability Details
File Location:
scripts/skillguard.py:348-351,scripts/skillguard.py:588-604,scripts/skillguard.py:620-657, andscripts/skillguard.py:1521-1528
Vulnerability Type: Unrestricted symlink following and unauthorized local-file access
Risk Level: HighVulnerable Code
python def safe_read(path, max_size=DEFAULT_MAX_FILE_SIZE): try: if path.stat().st_size > max_size: return None return path.read_text(errors="replace") except Exception: return Nonepython def _collect_files(self): try: for p in self.skill_dir.rglob("*"): if p.is_file() and not any(part.startswith('.git') and part != '.clawhub' for part in p.parts): # Max depth check if self.max_depth is not None: rel = p.relative_to(self.skill_dir) if len(rel.parts) > self.max_depth: continue # Max file size check try: if p.stat().st_size > self.max_file_size: continue except Exception: pass self.files.append(p) except Exception: passpython for f in self.files: if is_scannable(f): text = safe_read(f, self.max_file_size) if text is None: continue lines = text.split('\n') ext = f.suffix.lower()python def _check_symlinks(self): for f in self.files: if f.is_symlink(): target = os.readlink(f) rel = str(f.relative_to(self.skill_dir)) self.add(CRITICAL, f"Symlink detected: {rel} -> {target}", "", 0, recommendation="Symlinks can point to sensitive system files — verify target", score_override=25, check_id="SS-031")Technical Analysis
The scanner recursively collects files using
Path.rglob(). ThePath.is_file()an ...[truncated 2206 chars]- Remediation
View remediation
Remediation Suggestions
-
Reject symbolic links before any file metadata or content operation:
python if p.is_symlink(): continue -
Resolve both the scan root and every candidate, then enforce containment:
python root = self.skill_dir.resolve(strict=True) candidate = p.resolve(strict=True) try: candidate.relative_to(root) except ValueError: continue -
Use
lstat()rather thanstat()when examining directory entries so metadata checks do not follow links. -
Where supported, open files with no-follow semantics such as
O_NOFOLLOW, then verify the opened descriptor represents a regular file. -
Apply the same containment and no-follow policy to:
safe_read()sha256_file()check_binary_magic()- SBOM generation
- Interactive source-detail display
- Baseline hash generation
-
Perform symlink rejection during collection, before content scanning, rather than merely recording a finding afterward.
-
Add regression tests covering:
- Absolute links to sensitive files.
- Relative links using
../. - Links whose targets change between validation and opening.
- Nested symlink chains.
- Links to directories and special files.
-
