T09 · Insecure Skill Coding Practices
- Location
scripts/bgm_manager.py:203- Finding
Path Traversal in BGM Upload and Removal Operations
- Content
View full analysis
Tuple[bool, str]: """Remove a BGM file from a style directory. Args: style_name: BGM style directory name filename: Filename to remove Returns: (success, message) """ if style_name not in BGM_CONFIG["styles"]: return False, f"Unknown style: '{style_name}'" style_dir = get_style_dir(style_name) file_path = os.path.join(style_dir, filename) if not os.path.exists(file_path): return False, f"File not found: {file_path}" # Safety check: only allow audio file extensions if not filename.lower().endswith(('.mp3', '.m4a', '.wav')): return False, f"Refusing to remove non-audio file: {filename}" os.remove(file_path) return True, f"Removed: {file_path}" ``` ### Technical Analysis The removal function directly joins an attacker-controlled filename to the configured BGM style directory. It does not reject absolute paths, parent-directory components, or path separators, and it does not verify that the resolved path remains inside the intended BGM directory. The extension check does not prevent traversal. For example, `../../target.mp3 ...[truncated 2237 chars]- Remediation
View remediation
Path: if not filename or filename != Path(filename).name: raise ValueError("Only a plain filename is allowed") if "/" in filename or "\\" in filename or ".." in Path(filename).parts: raise ValueError("Path separators and parent traversal are forbidden") if Path(filename).suffix.lower() not in {".mp3", ".m4a", ".wav"}: raise ValueError("Unsupported audio extension") base = Path(style_dir).resolve(strict=True) candidate = (base / filename).resolve(strict=False) if not candidate.is_relative_to(base): raise ValueError("Destination escapes the BGM directory") return candidate ``` Apply the same confinement helper before `os.remove()`, `shutil.copy2()`, and force-overwrite handling. ]]>
