T09 · Insecure Skill Coding Practices
- Location
scripts/manage_sticker.py:12- Finding
Path Traversal Permits Arbitrary File Deletion and Movement
- Content
View full analysis
str: ext = os.path.splitext(name)[1].lower() if ext in SUPPORTED_EXTS: return name for candidate_ext in SUPPORTED_EXTS: candidate = name + candidate_ext if os.path.exists(os.path.join(TARGET_DIR, candidate)): return candidate return name + '.jpg' def delete_sticker(name, lang=None): name = resolve_name(name) filepath = os.path.join(TARGET_DIR, name) if not os.path.exists(filepath): return False, t('delete_missing', lang, name=name) os.remove(filepath) return True, t('delete_success', lang, name=name) def rename_sticker(old_name, new_name, lang=None): old_name = resolve_name(old_name) old_ext = os.path.splitext(old_name)[1].lower() or '.jpg' if os.path.splitext(new_name)[1].lower() not in SUPPORTED_EXTS: new_name += old_ext old_path = os.path.join(TARGET_DIR, old_name) new_path = os.path.join(TARGET_DIR, new_name) if not os.path.exists(old_path): return False, t('rename_missing', lang, name=old_name) if os.path.exists(new_path): return False, t('rename_exists', lang, name=new_name) shutil.move(old_path, new_path) return True, t('rename_success', lang, old=old_name, new=new_name) ``` ### Technical Analysis `resolve_name()` returns a supplied name unchanged whenever its extension is one of the supported media extensions. It does not reject absolute paths, path separators, or `..` components. The resulting value is joined to `TARGET_DIR` and passed directly to `os.remove()` or `shutil.move()`. Joining a base directory with traversal components does not guarantee containment. An absolute second argument can also cause `os.path.join()` to discard the base directory entirely. Both sou ...[truncated 1257 chars]- Remediation
View remediation
str: if not name or name != os.path.basename(name): raise ValueError("Sticker names must not contain path components") if os.path.isabs(name): raise ValueError("Absolute paths are not allowed") return name ``` 2. Resolve and validate every path before accessing it: ```python root = Path(TARGET_DIR).resolve() candidate = (root / validated_name).resolve() if not candidate.is_relative_to(root): raise ValueError("Path escapes the sticker library") ``` 3. Apply containment validation independently to rename source and destination paths. 4. Reject `/`, `\`, null bytes, `.` and `..` path components. 5. Consider rejecting symlinks or ensuring the final resolved target remains within the library. 6. Add tests covering relative traversal, absolute paths, nested separators, symlink escapes, and valid library operations. ]]>
