T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/generate_map.py:51
- Finding
- Path Traversal Through Unsanitized Output Filenames<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_map.py:51-64` **Vulnerability Type**: Path traversal and arbitrary file write **Risk Level**: High **Category**: T09: Insecure Skill Coding Practices ### Vulnerable Code ```python out_dir = Path(args.output_dir) if args.output_dir else OUTPUT_DIR out_dir.mkdir(parents=True, exist_ok=True) if not args.html_only: taskbook = generate_text_taskbook(data) taskbook_file = ( out_dir / f"{data.game_name}-任务书-{data.date_range or '待定'}.txt" ) taskbook_file.write_text(taskbook, encoding="utf-8") print(f"✅ 任务书已生成:{taskbook_file}") if not args.text_only: html = generate_html(data) html_file = ( out_dir / f"{data.game_name}-冒险地图-{data.date_range or '待定'}.html" ) html_file.write_text(html, encoding="utf-8") ``` ### Technical Analysis The generated filenames include `data.game_name` and `data.date_range`, both of which originate from supplied JSON. `TripData.validate()` checks whether the game name is present but does not reject path separators, `..` traversal components, absolute paths, control characters, or platform-specific path syntax. `pathlib.Path` does not automatically constrain the resulting path to `out_dir`. A value containing traversal components can therefore cause `write_text()` to resolve outside the intended output directory. This contradicts the documented claim that generated files are restricted to the current working directory. No final resolved-path containment check is performed before writing. ### Attack Path 1. An attacker influences the user prompt, collected trip data, or JSON supplied through `--stdin` or `--data`. 2. The attacker supplies a crafted `game_name` or `date_range` containing traversal components, such as `../../target`. 3. `TripData` accepts the value because validation only checks that `game_name` is nonempty. 4. `generate_map.py` directly incorporates the value into the destination path. 5. `write_tex ...[truncated 914 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Convert all user-controlled filename components to strict safe slugs containing only an allowlisted character set. 2. Explicitly reject `/`, `\`, `..`, null bytes, control characters, drive prefixes, and absolute-path syntax. 3. Resolve the final destination and enforce containment beneath the output directory: ```python import re from pathlib import Path def safe_filename_component(value: str) -> str: value = re.sub(r"[^A-Za-z0-9._-]+", "_", value).strip("._") if not value: raise ValueError("Invalid filename component") return value[:100] base = out_dir.resolve() game = safe_filename_component(data.game_name) date = safe_filename_component(data.date_range or "undated") destination = (base / f"{game}-adventure-map-{date}.html").resolve() if base not in destination.parents: raise ValueError("Output path escapes the configured directory") ``` 4. Consider exclusive file creation or explicit overwrite confirmation to prevent accidental replacement of existing files. 5. Add tests for Unix traversal, Windows separators and drive paths, absolute paths, Unicode separators, empty sanitized values, and excessively long names. ]]>
