T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- scripts/egress.py:341
- Finding
- Path Traversal Allows Source Files Outside the Skills Directory to Be Modified<![CDATA[ ## Vulnerability Details **File Location**: `scripts/egress.py`, lines 341-369 **Vulnerability Type**: Insufficient path validation and workspace boundary bypass **Risk Level**: High ### Vulnerable Code ```python def cmd_block(ws, skill_name): sd = ws / "skills" skill_dir = sd / skill_name if not skill_dir.is_dir(): if (sd / (QUARANTINE_PREFIX + skill_name)).is_dir(): print(f"Skill '{skill_name}' is quarantined. Unquarantine first."); sys.exit(1) print(f"Skill not found: {skill_name}"); _print_skills(sd); sys.exit(1) if skill_name in SELF_SKILL_DIRS: print(f"Cannot block self: {skill_name}"); sys.exit(1) actionable = [f for f in scan_skill(ws, skill_name, load_allowlist(ws)) if f["risk"] in ("CRITICAL", "HIGH")] if not actionable: print(f"No CRITICAL or HIGH findings in '{skill_name}'. Nothing to block."); return 0 by_file = {} for f in actionable: by_file.setdefault(f["file"], []).append(f) total = files_mod = 0 print("=" * 60); print(f"BLOCKING NETWORK CALLS IN: {skill_name}"); print("=" * 60); print() for rel, ffindings in sorted(by_file.items()): ap = ws / rel if not ap.is_file(): continue if ap.suffix not in CODE_SUFFIXES: for ff in ffindings: if ff["url"]: print(f" [FLAGGED] {rel}:{ff['line']} — {ff['reason']} (non-code, manual review)") continue indices = {ff["line"] - 1 for ff in ffindings} cnt = _block_lines(ap, indices) if cnt: total += cnt; files_mod += 1 print(f" [BLOCKED] {rel}: {cnt} line(s) neutralized (backup: {ap.suffix}.bak)") print(f"\nTotal: {total} line(s) blocked across {files_mod} file(s)") if total: print("Backups created with .bak extension.\n") return 0 ``` The same unvalidated construction is also used when collecting files: ```python def collect_skill_files(ws, ...[truncated 2758 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Restrict Skill names to a conservative identifier format, for example: ```python if not re.fullmatch(r"[A-Za-z0-9_.-]+", skill_name): raise ValueError("Invalid skill name") ``` 2. Explicitly reject absolute paths, `..`, path separators, and empty names. 3. Resolve and validate the canonical target before scanning: ```python skills_root = (ws / "skills").resolve(strict=True) skill_dir = (skills_root / skill_name).resolve(strict=True) if skill_dir.parent != skills_root: raise ValueError("Skill must be an immediate child of the skills directory") ``` 4. Reject symbolic-link Skill directories and symbolic-link files before reading or writing them. 5. Before every write, resolve the file again and verify that it remains beneath the validated Skill directory using `Path.is_relative_to()` or an equivalent compatibility helper. 6. Open files using link-resistant operating-system facilities where available to reduce time-of-check/time-of-use risks. 7. Apply equivalent validation to `block`, `quarantine`, `unquarantine`, and every other command accepting a Skill name. 8. Add regression tests for `../`, nested traversal, absolute paths, symlinked directories, and symlinked files. ]]>
