T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- scripts/integrity.py:616
- Finding
- Path Traversal Allows Restore Operations Outside the Workspace<![CDATA[ ## Vulnerability Details **File Location**: `scripts/integrity.py`, lines 616-626 **Vulnerability Type**: Improper path confinement in filesystem restore operation **Risk Level**: High ### Vulnerable Code ```python def cmd_restore(workspace: Path, filepath: str): """Restore a file from its baseline snapshot.""" rel = filepath.replace("\\", "/") snap = get_snapshot_path(workspace, rel) if snap is None: print(f"No snapshot found for: {rel}") print("Only critical, config, and skill files are snapshotted.") sys.exit(1) dest = workspace / rel import shutil shutil.copy2(snap, dest) ``` ### Technical Analysis The user-controlled `filepath` is normalized only by replacing backslashes. The implementation does not reject absolute paths or `..` path components, resolve the resulting path, or verify that the resolved destination remains inside `workspace`. The snapshot source is formed relative to `.integrity/snapshots`, while the destination is formed relative to the workspace root. Because these base paths have different depths, a traversal path can resolve the source and destination to different locations outside their intended roots. If the resolved source exists as a regular file, `shutil.copy2()` can copy it to an unintended destination outside the workspace. The same unvalidated path construction pattern also appears in `cmd_accept` at lines 586-587 and `cmd_rollback` at lines 645-646. Although those operations have additional constraints, all file-oriented commands should share a single strict path-validation routine. ### Attack Path 1. An attacker influences the file argument passed to `restore`, such as through malicious workspace instructions or an unsafe Agent-generated tool call. 2. The argument contains enough `../` components to escape `.integrity/snapshots` and the workspace. 3. `get_snapshot_path()` resolves the source traversal and accepts it if the resulting path is a file. 4. `worksp ...[truncated 865 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Reject absolute paths and any path containing `..` before performing filesystem operations. - Resolve the workspace, snapshot root, source, and destination with `Path.resolve()`. - Enforce containment using `Path.is_relative_to()` or an equivalent compatibility helper: ```python def confined_path(root: Path, user_path: str) -> Path: relative = Path(user_path.replace("\\", "/")) if relative.is_absolute() or ".." in relative.parts: raise ValueError("Absolute paths and traversal are not allowed") root = root.resolve() candidate = (root / relative).resolve() if not candidate.is_relative_to(root): raise ValueError("Path escapes the authorized root") return candidate ``` - For `restore`, accept only relative paths already recorded in the integrity manifest. - Independently validate the snapshot source under `.integrity/snapshots` and the destination under the workspace. - Apply the same centralized validation to `accept`, `restore`, and `rollback`. - Revalidate paths immediately before writing to reduce time-of-check/time-of-use exposure. ]]>
