T05 · Unauthorized Access and Privilege Escalation
- Location
scripts/fis_lifecycle_pro.py:279- Finding
Path Traversal in Ticket File Operations
- Content
View full analysis
Vulnerability Details
File Location:
scripts/fis_lifecycle_pro.py:279-306
Vulnerability Type: Unsanitized path construction using attacker-controlled ticket identifiers
Risk Level: HighVulnerable 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 TrueThe same unsafe path-construction pattern also appears in the following operations:
update_status:scripts/fis_lifecycle_pro.py:163complete_ticket:scripts/fis_lifecycle_pro.py:202,226archive_ticket:scripts/fis_lifecycle_pro.py:282-302get_ticket:scripts/fis_lifecycle_pro.py:349
Technical Analysis
The
ticket_idvalue 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.joindoes not enforce containment within the intended ticket directory. As a result, a value such as../../targetcan resolve totarget.jsonoutside the active, completed, or archived ticket directory.The affected methods perform sensitive operations ...[truncated 2189 chars]
- Remediation
View remediation
Remediation Suggestions
- 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- 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-
Use the centralized helper in
update_status,complete_ticket,archive_ticket, andget_ticketrather than constructing paths independently. -
Reject identifiers containing
/,\,.., null bytes, or absolute-path syntax, even if additional validation is introduced elsewhere. -
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.
-
Run the Skill under a dedicated, minimally privileged account that cannot modify unrelated OpenClaw configuration or user files.
-
Add negative tests covering traversal identifiers, absolute paths, mixed separators, symlink escapes, and malformed ticket IDs.
