T09 · Insecure Skill Coding Practices
- Location
scripts/task_manager.py:41- Finding
Project Name Path Traversal Allows JSON Files Outside the Task Directory to Be Read or Overwritten
- Content
View full analysis
str: return os.path.join(TASKS_DIR, f"{project}.json") def load_project(project: str) -> dict: path = task_file(project) if not os.path.exists(path): print(f"Error: project '{project}' not found at {path}", file=sys.stderr) sys.exit(1) with open(path) as f: return json.load(f) def save_project(project: str, data: dict): path = task_file(project) os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, "w") as f: json.dump(data, f, indent=2, ensure_ascii=False) ``` ### Technical Analysis The user-controlled `project` argument is incorporated directly into a filesystem path without validation or a containment check. `os.path.join()` does not guarantee that the resulting path remains beneath `TASKS_DIR`: - An absolute project name causes `os.path.join()` to discard `TASKS_DIR`. - A project name containing `../` components can traverse outside the intended directory. - The `.json` suffix limits targets to filenames ending in `.json`, but does not prevent access to sensitive JSON configuration or state files. - `save_project()` creates parent directories and writes directly to the resolved path. - `init --force` permits replacement of an existing target. The effective access is limited to files readable or writable by the operating-system account running the Skill. No privilege escalation beyond that account was identified. ### Attack Path 1. An attacker supplies a crafted project argument through a command that loads or saves project data. 2. For disclosure, the attacker uses an absolute or traversal-based name, for example: ```bash python3 scripts/task_manager.py status /tmp/sensitive --json ``` 3. The CLI resolves the path as `/tmp/se ...[truncated 1073 chars]- Remediation
View remediation
str: if not PROJECT_RE.fullmatch(project): raise ValueError("Invalid project identifier") return project ``` 2. Resolve and verify the final path remains under the configured task directory: ```python def task_file(project: str) -> str: validate_project(project) base = os.path.realpath(TASKS_DIR) candidate = os.path.realpath(os.path.join(base, f"{project}.json")) if os.path.commonpath([base, candidate]) != base: raise ValueError("Project path escapes task directory") return candidate ``` 3. Explicitly reject absolute paths, path separators, `.` components, and `..` components. 4. Avoid exposing full resolved paths in errors unless needed for trusted diagnostics. 5. Require an explicit confirmation or safer replacement policy before overwriting existing files. 6. Add tests covering absolute paths, traversal sequences, nested paths, symbolic-link edge cases, and platform-specific separators. ]]>
