T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/task_logger.py:113
- Finding
- Path Traversal Enables JSON File Access and Deletion Outside the Snapshot Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/task_logger.py`, lines 113–122 and 211–224 **Vulnerability Type**: Path traversal and unsafe file deletion **Risk Level**: High ### Vulnerable Code ```python def load_snapshot(snap_id: str) -> dict: snap_path = os.path.join(SNAPSHOT_DIR, f"{snap_id}.json") with open(snap_path) as f: return json.load(f) def delete_snapshot(snap_id: str): snap_path = os.path.join(SNAPSHOT_DIR, f"{snap_id}.json") if os.path.exists(snap_path): os.remove(snap_path) ``` The vulnerable functions are reached directly through the `end` command: ```python def cli_end(snap_id: str, status: str = "ok"): snap = load_snapshot(snap_id) after = read_session_totals() entry = log_task( task_type=snap["task_type"], description=snap["description"], before=snap["totals_before"], after=after, timestamp_start=snap["timestamp_start"], timestamp_end=datetime.now(timezone.utc).isoformat(), status=status ) delete_snapshot(snap_id) print(f"[task_logger] LOGGED: {snap['task_type']} | ${entry['cost_usd']:.4f} | " f"{entry['calls']} calls | {entry['output_tokens']:,} out tokens") ``` ```python elif cmd == "end": snap_id = sys.argv[2] status = sys.argv[3] if len(sys.argv) > 3 else "ok" cli_end(snap_id, status) ``` ### Technical Analysis The `snap_id` command-line argument is incorporated into a filesystem path without validation or canonicalization. The code assumes that the value is an eight-character UUID fragment, but this constraint is only applied when the program creates a snapshot. It is not enforced when a snapshot is loaded or deleted. An attacker can supply path separators, `..` components, or an absolute path. In Python, if the later operand supplied to `os.path.join()` is absolute, the preceding `SNAPSHOT_DIR` is discarded. The `.json` suffix limits the affected filenames to JSON paths, but it ...[truncated 2338 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Strictly validate snapshot identifiers before performing any filesystem operation: ```python import re SNAPSHOT_ID_RE = re.compile(r"^[0-9a-f]{8}$") def validate_snapshot_id(snap_id: str) -> str: if not SNAPSHOT_ID_RE.fullmatch(snap_id): raise ValueError("Invalid snapshot ID") return snap_id ``` 2. Resolve the candidate path and verify that it remains under the snapshot directory: ```python def snapshot_path(snap_id: str) -> str: validate_snapshot_id(snap_id) base = os.path.realpath(SNAPSHOT_DIR) candidate = os.path.realpath(os.path.join(base, f"{snap_id}.json")) if os.path.commonpath([base, candidate]) != base: raise ValueError("Snapshot path escapes snapshot directory") return candidate ``` 3. Use the validated helper consistently in `save_snapshot()`, `load_snapshot()`, and `delete_snapshot()`. 4. Track snapshots created by the current process or maintain a trusted index. Refuse to delete a file unless its identifier is present in that trusted state. 5. Reject symlinks where supported. Open files using secure flags such as `O_NOFOLLOW`, and verify the opened file is a regular file before reading or deleting it. 6. Validate the JSON schema after loading, including the types and permitted values of every field. 7. Run the utility under a dedicated, unprivileged account with write access only to its log and snapshot directories. 8. Add tests covering absolute paths, nested traversal, encoded or mixed path separators, symlinks, malformed IDs, and incompatible snapshot documents. ]]>
