T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- scripts/fis_lifecycle_pro.py:279
- Finding
- Path Traversal in Ticket File Operations## Vulnerability Details **File Location**: `scripts/fis_lifecycle_pro.py:279-306` **Vulnerability Type**: Unsanitized path construction using attacker-controlled ticket identifiers **Risk Level**: High ### Vulnerable Code ```python def archive_ticket(self, ticket_id): """Archive a completed ticket""" # Check completed first src = os.path.join(self.completed_dir, f"{ticket_id}.json") if not os.path.exists(src): # Check active src = os.path.join(self.active_dir, f"{ticket_id}.json") if not os.path.exists(src): print(f"✗ Ticket not found: {ticket_id}") return False with open(src, "r") as f: ticket = json.load(f) ticket["status"] = "archived" ticket["archived_at"] = datetime.now().isoformat() ticket["updated_at"] = datetime.now().isoformat() with open(src, "w") as f: json.dump(ticket, f, indent=2) # Move to archive dst = os.path.join(self.archive_dir, f"{ticket_id}.json") os.rename(src, dst) print(f"⚪ Archived: {ticket_id}") return True ``` The same unsafe path-construction pattern also appears in the following operations: - `update_status`: `scripts/fis_lifecycle_pro.py:163` - `complete_ticket`: `scripts/fis_lifecycle_pro.py:202,226` - `archive_ticket`: `scripts/fis_lifecycle_pro.py:282-302` - `get_ticket`: `scripts/fis_lifecycle_pro.py:349` ### Technical Analysis The `ticket_id` value originates from CLI arguments and is inserted directly into filesystem paths: ```python os.path.join(self.active_dir, f"{ticket_id}.json") ``` No validation prevents the identifier from containing absolute paths, `..` traversal components, path separators, or symlink-based escapes. `os.path.join` does not enforce containment within the intended ticket directory. As a result, a value such as `../../target` can resolve to `target.json` outside the active, completed, or archived ticket directory. The affected methods perform sensitive operations ...[truncated 2189 chars]
- Remediation
- ## Remediation Suggestions 1. Enforce a strict ticket identifier format before any filesystem operation. For example: ```python import re TICKET_ID_PATTERN = re.compile(r"^TASK_[A-Za-z0-9_]+$") def validate_ticket_id(ticket_id: str) -> str: if not TICKET_ID_PATTERN.fullmatch(ticket_id): raise ValueError("Invalid ticket identifier") return ticket_id ``` 2. Centralize path construction and verify containment after canonicalization: ```python from pathlib import Path def safe_ticket_path(directory: str, ticket_id: str) -> Path: validate_ticket_id(ticket_id) base = Path(directory).resolve() candidate = (base / f"{ticket_id}.json").resolve() if candidate.parent != base: raise ValueError("Ticket path escapes the ticket directory") return candidate ``` 3. Use the centralized helper in `update_status`, `complete_ticket`, `archive_ticket`, and `get_ticket` rather than constructing paths independently. 4. Reject identifiers containing `/`, `\`, `..`, null bytes, or absolute-path syntax, even if additional validation is introduced elsewhere. 5. Account for symlink attacks. Where practical, ensure ticket directories and files are not symlinks and use secure descriptor-based file operations with no-follow behavior. 6. Run the Skill under a dedicated, minimally privileged account that cannot modify unrelated OpenClaw configuration or user files. 7. Add negative tests covering traversal identifiers, absolute paths, mixed separators, symlink escapes, and malformed ticket IDs.
