T09 · Insecure Skill Coding Practices
- Location
scripts/namer.py:87- Finding
Unrestricted File Write Through Path Traversal and Absolute Paths
- Content
View full analysis
Vulnerability Details
File Location:
scripts/namer.py:39-55,scripts/namer.py:87-123, andscripts/reporter.py:21-58
Vulnerability Type: Path traversal and unrestricted file write
Risk Level: HighVulnerable Code
python def _generate_train_name(self, ticket: TicketInfo) -> str: """火车票命名: {person}-{date}-{amount}-中国铁路""" person = ticket.person or "未知" date = ticket.date or "未知" amount = ticket.amount or "未知" ext = Path(ticket.original_name).suffix return f"{person}-{date}-{amount}-中国铁路{ext}" def _generate_didi_name(self, ticket: TicketInfo) -> str: """打车发票命名: {company}-{date}-{invoice_no}-{amount}-{seller}-发票""" company = COMPANY_NAME date = ticket.date or "未知" invoice_no = ticket.invoice_no or "未知" amount = ticket.amount or "未知" seller = ticket.seller or "未知" ext = Path(ticket.original_name).suffix return f"{company}-{date}-{invoice_no}-{amount}-{seller}-发票{ext}"python def rename_file(self, ticket: TicketInfo, create_subdir: bool = True) -> TicketInfo: """重命名并移动文件""" if ticket.status != ProcessStatus.SUCCESS: return ticket # 生成新文件名 new_name = self.generate_name(ticket) ticket.new_name = new_name # 确定输出子目录 if create_subdir: type_dir = self.output_dir / ticket.file_type.value type_dir.mkdir(parents=True, exist_ok=True) output_path = type_dir / new_name else: output_path = self.output_dir / new_name # 处理文件名冲突 if output_path.exists(): base_name = Path(new_name).stem ext = Path(new_name).suffix counter = 1 while output_path.exists(): new_name = f"{base_name}_{counter}{ext}" output_path = (type_dir if create_subdir else self.output_dir) / new_name counter += 1 ticket.new_name = new_name # 复制文件(原文件保留) try: ...[truncated 3994 chars]- Remediation
View remediation
Remediation Suggestions
-
Validate every filename component against a strict allowlist appropriate for dates, amounts, invoice numbers, personal names, and seller names.
-
Replace or reject
/,\, null bytes, drive prefixes, UNC prefixes, and parent-directory components. -
Reject absolute paths and require report filenames to be plain basenames with an approved
.xlsxextension. -
Resolve both the output root and candidate destination before writing, then verify confinement:
python output_root = self.output_dir.resolve() destination = (output_root / safe_name).resolve() if output_root not in destination.parents: raise ValueError("Destination escapes the configured output directory") -
Use
Path(value).nameonly as an additional safeguard, not as a substitute for validation. -
Build names from individually sanitized components and enforce a maximum filename length.
-
Open destination files using exclusive-creation semantics where feasible to prevent unintended overwrites and race conditions.
-
Apply the same confinement validation to
ReportGenerator.generate_excel(). -
Add tests for absolute POSIX paths, Windows drive paths, UNC paths, mixed separators, nested traversal sequences, and encoded or Unicode separator variants.
-
