T05 · Unauthorized Access and Privilege Escalation
Warning
- Location
- scripts/minivcs/minivcs.py:493
- Finding
- Unrestricted File Operations Outside the Project Root<![CDATA[ ## Vulnerability Details **File Location**: `scripts/minivcs/minivcs.py:493-496`, `scripts/minivcs/minivcs.py:516-517`, `scripts/minivcs/minivcs.py:641-642`, `scripts/minivcs/minivcs.py:704-705`, `scripts/minivcs/minivcs.py:734-739` **Vulnerability Type**: Missing project-boundary validation for file read, move, and overwrite operations **Risk Level**: Medium ### Vulnerable Code ```python def _get_relative_path(self, absolute_path: str) -> str: if absolute_path.startswith(self.project_root + os.sep): return absolute_path[len(self.project_root) + 1 :] return absolute_path ``` The modification and deletion operations accept the resulting unrestricted path: ```python def record_modify(self, file_path: str) -> Dict[str, Any]: abs_path = os.path.abspath(file_path) if not os.path.exists(abs_path): return {"success": False, "error": f"File not found: {file_path}"} ``` ```python def record_delete(self, file_path: str) -> Dict[str, Any]: abs_path = os.path.abspath(file_path) if not os.path.exists(abs_path): return {"success": False, "error": f"File not found: {file_path}"} ``` Restore operations preserve absolute paths and can write directly to them: ```python file_path = record.get("filePath", "") target_path = file_path if os.path.isabs(file_path) else os.path.join(self.project_root, file_path) success = self.file_manager.restore_from_trash(trash_file, target_path) ``` ```python file_path = record.get("filePath", "") target_path = file_path if os.path.isabs(file_path) else os.path.join(self.project_root, file_path) os.makedirs(os.path.dirname(target_path), exist_ok=True) with open(target_path, "w", encoding="utf-8") as f: f.write(content) ``` ### Technical Analysis `project_root` is used only to convert paths into relative record names. It is not enforced as an authorization boundary. If a target is outside the project, `_get_relative_path()` deliberately returns its absolute path. As a result, th ...[truncated 2432 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Enforce project-root containment before every read, move, copy, restore, or overwrite: ```python project_root = os.path.realpath(self.project_root) target = os.path.realpath(file_path) try: contained = os.path.commonpath([project_root, target]) == project_root except ValueError: contained = False if not contained: return {"success": False, "error": "Path is outside the project root"} ``` 2. Apply this check independently in: - `record_modify()` - `_record_binary_backup()` - `record_delete()` - `restore_file()` - History and merge operations that derive paths from user input. 3. Resolve symbolic links and reject targets whose canonical paths leave the authorized root. 4. If protecting external files is a required feature, separate it from normal project operations: - Require an explicit `--allow-external-path` option. - Require a canonical-path allowlist. - Display a high-risk warning for credential and system directories. - Require fresh confirmation for each external target. 5. Do not run the Skill with administrative or root privileges unless a narrowly scoped operation explicitly requires them. 6. Validate paths recovered from `logs.json` before restore. Treat the log as untrusted state and reject absolute or escaping paths unless they were explicitly authorized. ]]>
