T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/svg_composer.py:858
- Finding
- Arbitrary File Write Through Unsanitized Preview Filenames<![CDATA[ ## Vulnerability Details **File Location**: `scripts/svg_composer.py:858-863` **Vulnerability Type**: Path traversal and arbitrary file write **Risk Level**: High ### Vulnerable Code ```python # 保存 SVG 文件 saved_files = [] for label, filename, svg_content in svg_list: filepath = os.path.join(output_dir, filename) with open(filepath, 'w', encoding='utf-8') as f: f.write(svg_content) saved_files.append((label, filename, filepath)) ``` ### Technical Analysis The public `generate_preview_html()` function accepts an `svg_list` containing caller-controlled filenames and content. Each filename is passed directly to `os.path.join()` and then to `open()` in write mode. `os.path.join()` does not enforce directory confinement: - A filename containing `../` can traverse outside `output_dir`. - An absolute filename can cause the supplied `output_dir` to be discarded. - Existing files are silently truncated and overwritten by `"w"` mode. There is no basename restriction, canonical path validation, extension enforcement, or check that the resolved destination remains under the intended output directory. ### Attack Path 1. An attacker obtains control over, or influences, the `svg_list` argument passed to `generate_preview_html()`. 2. The attacker supplies an entry such as: ```python ( "horizontal", "../../attacker-controlled.svg", "<svg xmlns='http://www.w3.org/2000/svg'></svg>" ) ``` 3. The function joins the malicious filename with `output_dir`. 4. The resulting path resolves outside the designated output directory. 5. The function opens that path in write mode and writes attacker-controlled content. 6. If the destination already exists, it is overwritten. ### Impact Assessment An attacker can create or overwrite files anywhere writable by the Python process. The exact scope is limited to the operating-system privileges of the process running the Skill; the code does not independently elevate privilege ...[truncated 403 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Reject absolute filenames and any filename containing path separators or traversal components. 2. Accept only a basename generated or validated by the application: ```python from pathlib import Path output_root = Path(output_dir).resolve() safe_name = Path(filename).name if safe_name != filename or not safe_name.lower().endswith(".svg"): raise ValueError("Invalid SVG filename") destination = (output_root / safe_name).resolve() if output_root not in destination.parents: raise ValueError("Destination escapes output directory") ``` 3. Prefer generating filenames internally instead of accepting them from callers. 4. Use exclusive creation mode (`"x"`) when overwriting is not explicitly required. 5. Apply file-count and output-size limits. 6. If overwriting is required, explicitly authorize the destination and use an atomic temporary-file replacement strategy. ]]>
