T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/create_skill.py:91
- Finding
- Arbitrary File Write Through Unsanitized Resource Names## Vulnerability Details **File Location**: `scripts/create_skill.py:91-131` **Vulnerability Type**: Path traversal and unrestricted file write **Risk Level**: High ### Vulnerable Code ```python # Create scripts if scripts: scripts_dir = skill_path / "scripts" scripts_dir.mkdir(exist_ok=True) for script in scripts: script_name = script.get("name", "script.py") script_content = script.get("content", "") script_path = scripts_dir / script_name script_path.write_text(script_content, encoding="utf-8") # Make executable if Python/Shell if script_name.endswith(('.py', '.sh')): script_path.chmod(0o755) result["files_created"].append(f"scripts/{script_name}") # Create references if references: refs_dir = skill_path / "references" refs_dir.mkdir(exist_ok=True) for ref in references: ref_name = ref.get("name", "reference.md") ref_content = ref.get("content", "") ref_path = refs_dir / ref_name ref_path.write_text(ref_content, encoding="utf-8") result["files_created"].append(f"references/{ref_name}") # Create assets if assets: assets_dir = skill_path / "assets" assets_dir.mkdir(exist_ok=True) for asset in assets: asset_name = asset.get("name", "asset") asset_content = asset.get("content", "") asset_path = assets_dir / asset_name # Handle binary content (base64) if asset.get("encoding") == "base64": import base64 asset_path.write_bytes(base64.b64decode(asset_content)) else: asset_path.write_text(asset_content, encoding="utf-8") ...[truncated 2556 chars]
- Remediation
- ## Remediation Suggestions - Treat all resource names as untrusted input. - Reject absolute paths, empty names, `.` and `..` components, path separators, and platform-specific alternate separators. - If nested resource paths are unnecessary, require a basename and enforce a conservative allowlist such as letters, digits, periods, underscores, and hyphens. - Resolve the resource root and destination before writing, then verify containment: ```python def safe_destination(root: Path, supplied_name: str) -> Path: if not supplied_name or Path(supplied_name).is_absolute(): raise ValueError("Resource name must be a non-empty relative path") root = root.resolve() destination = (root / supplied_name).resolve() try: destination.relative_to(root) except ValueError as exc: raise ValueError("Resource path escapes its destination directory") from exc return destination ``` - Account for symbolic-link attacks when the output tree may be writable by another user. Avoid following existing symlinks and consider descriptor-relative, no-follow file operations on supported platforms. - Create files with exclusive semantics when overwriting existing files is not required. - Apply executable permissions only after validating the destination and only when explicitly requested, rather than inferring executability solely from a filename extension. - Add tests covering absolute paths, repeated traversal components, mixed path separators, symbolic links, and valid filenames.
