T09 · Insecure Skill Coding Practices
Error
- Location
- src/reporter.py:126
- Finding
- Arbitrary File Overwrite Through Unsanitized Report Filenames<![CDATA[ ## Vulnerability Details **File Location**: `src/reporter.py:126-212` **Vulnerability Type**: Path traversal and arbitrary file overwrite **Risk Level**: High ### Vulnerable Code ```python def generate_html_report(self, report: TestReport, filename: Optional[str] = None) -> str: """Generate HTML report.""" if filename is None: filename = f"test_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html" filepath = self.output_dir / filename template = Template(HTML_REPORT_TEMPLATE) html_content = template.render( timestamp=report.timestamp.strftime("%Y-%m-%d %H:%M:%S"), total=report.total, passed=report.passed, failed=report.failed, skipped=report.skipped, pass_rate=f"{report.pass_rate:.1f}", duration=f"{report.total_duration:.2f}", tests=[ { "name": r.name, "status": r.status, "duration": f"{r.duration:.3f}", "message": r.message } for r in report.results ] ) with open(filepath, "w") as f: f.write(html_content) return str(filepath) ``` The JSON and JUnit generators repeat the same unsafe construction: ```python filepath = self.output_dir / filename with open(filepath, "w") as f: json.dump(data, f, indent=2) ``` ```python filepath = self.output_dir / filename ... tree.write(filepath, encoding="utf-8", xml_declaration=True) ``` ### Technical Analysis The caller-supplied `filename` is joined directly to `self.output_dir` without validating whether it is an absolute path or contains traversal components such as `..`. `pathlib.Path` does not confine a joined path to its parent directory. An absolute filename causes the configured output directory to be discarded, while traversal components can resolve outside it. Each report generator then opens the resulting path in write mode or passes it to `ElementTree.write`, overwriting ...[truncated 1564 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Restrict caller-provided filenames to a single basename: ```python candidate = Path(filename) if candidate.is_absolute() or candidate.name != filename: raise ValueError("Filename must be a basename without directory components") ``` 2. Resolve and verify the final path remains under the configured output directory: ```python base = self.output_dir.resolve() destination = (base / filename).resolve() if base not in destination.parents: raise ValueError("Report path escapes output directory") ``` 3. Enforce the expected extension for each report type. 4. Consider generating filenames internally rather than accepting arbitrary paths. 5. If replacing existing reports is unnecessary, use exclusive creation mode (`"x"`) to prevent accidental overwrite. 6. Apply the same centralized path-validation helper to HTML, JSON, JUnit XML, and Allure output. 7. Add regression tests covering: - `../` traversal - Nested traversal - Absolute POSIX and Windows paths - Symlinks that resolve outside the output directory - Attempts to overwrite existing files ]]>
