T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/batch_search.py:141
- Finding
- Arbitrary File Write Through Unsanitized Batch Query Names<![CDATA[ ## Vulnerability Details **File Location**: `scripts/batch_search.py`, lines 141 and 188–189 **Vulnerability Type**: Path traversal and arbitrary file overwrite **Risk Level**: Medium ### Vulnerable Code ```python query_name = query_spec.get("name", f"query_{index:03d}") ``` ```python if save_individual: output_file = self.output_dir / f"{result['query_name']}.json" save_json(result["results"], output_file) ``` The called `save_json` function creates parent directories and opens the destination in write mode: ```python filepath = Path(filepath) filepath.parent.mkdir(parents=True, exist_ok=True) with open(filepath, "w", encoding="utf-8") as f: json.dump(data, f, indent=indent, ensure_ascii=ensure_ascii, default=str) ``` ### Technical Analysis The batch query `name` is read directly from an attacker-controlled JSONL object. It is later interpolated into an output path without rejecting absolute paths, directory separators, or `..` traversal components. Python's `pathlib` discards the left operand when the right operand is absolute. Consequently, a name such as `/tmp/target` produces `/tmp/target.json`. A relative name such as `../../target` can similarly escape the configured output directory. Because `save_json` creates missing parent directories and opens the destination in write mode, the issue provides a constrained arbitrary file-write primitive. The destination must end in `.json`, and the written content consists of search results. ### Attack Path 1. An attacker supplies or causes the user to process a crafted JSONL file. 2. The file contains a valid query and a malicious name, for example: ```json {"query":"security","max_results":1,"name":"../../attacker-controlled"} ``` 3. `run_query` accepts the `name` without validation. 4. The arXiv query succeeds. 5. `run_batch` constructs a path outside `output_dir`. 6. `save_json` creates parent directories where possible and overwrites the selected writable `.json` fi ...[truncated 377 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Reject absolute paths and query names containing `/`, `\`, `..`, or platform-specific separators. - Convert query names to safe basenames using a strict allowlist such as `[A-Za-z0-9._-]`. - Resolve both the output directory and destination, then enforce containment before writing: ```python safe_name = re.sub(r"[^A-Za-z0-9._-]", "_", query_name) base = self.output_dir.resolve() destination = (base / f"{safe_name}.json").resolve() if not destination.is_relative_to(base): raise ValueError("Output path escapes the configured directory") ``` - Use exclusive creation where overwriting is unnecessary. - Consider assigning server-generated filenames instead of accepting filenames from input data. - Add tests covering absolute paths, nested traversal, mixed separators, and symbolic-link escape scenarios. ]]>
