T09 · Insecure Skill Coding Practices
- Location
scripts/text_to_audio_api.py:34- Finding
Unsanitized Task IDs Permit Filesystem Path Traversal
- Content
View full analysis
bool: return (_get_log_dir() / f"{task_id}.sent").exists() def _mark_sent(task_id: str) -> None: (_get_log_dir() / f"{task_id}.sent").touch() def _save_task_text(task_id: str, text: str) -> None: try: (_get_log_dir() / f"{task_id}.prompt").write_text(text, encoding='utf-8') except Exception: pass def _load_task_text(task_id: str, truncate: bool = True) -> Optional[str]: try: f = _get_log_dir() / f"{task_id}.prompt" if f.exists(): text = f.read_text(encoding='utf-8').strip() if truncate: return text[:20] + "..." if len(text) > 20 else text return text except Exception: pass return None def _increment_query_count(task_id: str) -> int: f = _get_log_dir() / f"{task_id}.count" count = _get_query_count(task_id) + 1 f.write_text(str(count)) return count ``` The task identifier is obtained either from a command-line argument or from a remote API response: ```python if args.query: if not args.task_id: print("Error: query mode requires --task-id", file=sys.stderr) sys.exit(1) count = _increment_query_count(args.task_id) ``` ```python task_id = result.get("data", {}).get("task_id") submitted_at = datetime.now().strftime('%Y-%m-%d %H:%M:%S') _save_task_text(task_id, args.text) ``` ### Technical Analysis The application interpolates `task_id` directly into filenames without validating its character set, length, path separators, or resolved location. `pathlib.Path` does not automatically confine a joined path to its intended parent directory. A value containing traversal components such as `../../target` can therefore cause the resulting ...[truncated 1806 chars]- Remediation
View remediation
str: if not isinstance(task_id, str) or not TASK_ID_PATTERN.fullmatch(task_id): raise ValueError("Invalid task ID") return task_id ``` 2. Apply validation to both command-line and API-provided task IDs. 3. Resolve each constructed path and verify that it remains under the resolved log directory: ```python def task_file(task_id: str, suffix: str) -> Path: task_id = validate_task_id(task_id) base = _get_log_dir().resolve() candidate = (base / f"{task_id}{suffix}").resolve() if candidate.parent != base: raise ValueError("Task path escapes the state directory") return candidate ``` 4. Replace all direct path interpolation with the centralized `task_file` helper. 5. Reject missing or non-string API task IDs before saving task state. 6. Do not silently suppress filesystem exceptions; report them safely so validation and storage failures are observable. ]]>
