T05 · Unauthorized Access and Privilege Escalation
- Location
scripts/bundle-installer.py:173- Finding
Manifest-Controlled Skill Names Enable Arbitrary Directory Deletion During Uninstall
- Content
View full analysis
Vulnerability Details
File Location:
scripts/bundle-installer.py:130-136andscripts/bundle-installer.py:173-193
Vulnerability Type: Path traversal leading to arbitrary directory deletion
Risk Level: HighVulnerable Code
python for skill in skills: skill_name = skill.get("name", "") skill_path = skill.get("path", f"skills/{skill_name}") try: copy_skill(source_dir, skill_path, target_dir) installed_skills.append(skill_name) except SystemExit: errors.append(skill_name)The unvalidated names are subsequently saved in the installation registry and consumed during uninstallation:
python # Delete each skill directory for skill_name in bundle_info.get("skills", []): skill_dir = target_dir / skill_name if skill_dir.exists(): try: # Preserve user data directories user_data_dirs = [".learnings", ".cache", "user-data"] for ud in user_data_dirs: ud_path = skill_dir / ud if ud_path.exists(): # Move to an adjacent backup directory backup_name = f".{skill_name}_{ud}_bak" backup_path = target_dir / backup_name if backup_path.exists(): shutil.rmtree(str(backup_path)) shutil.move(str(ud_path), str(backup_path)) shutil.rmtree(str(skill_dir)) removed_skills.append(skill_name) except OSError as e: error_exit(f"Failed to delete skill ({skill_dir}): {e}")Technical Analysis
The
namefield of every skill is controlled by the bundle manifest. The installer does not require this value to be a simple directory name and does not reject absolute paths, path separators, or..traversal components.During installation, the untrusted value is saved in `.installed-bundles.json ...[truncated 1999 chars]
- Remediation
View remediation
Remediation Suggestions
-
Enforce a strict skill-name allowlist, such as
^[A-Za-z0-9][A-Za-z0-9_-]*$. -
Explicitly reject absolute names, path separators,
.and..components, and empty names. -
Canonicalize the target root and candidate directory before deletion:
python target_root = target_dir.resolve() skill_dir = (target_root / skill_name).resolve() if not skill_dir.is_relative_to(target_root) or skill_dir == target_root: error_exit("Unsafe skill path in installation registry") -
Store the actual validated installation directory name in the registry rather than the raw manifest field.
-
Treat
.installed-bundles.jsonas untrusted input and validate all existing registry entries before performing deletion or movement. -
Refuse to recursively delete symlinks or paths whose resolved targets escape the target root.
-
Perform all validation before making any filesystem changes, and abort the complete operation if any entry is unsafe.
-
Add regression tests covering absolute paths, nested traversal, malformed registry entries, separators, symlinks, and names that resolve to the target root itself.
-
