T09 · Insecure Skill Coding Practices
- Location
scripts/main.py:152- Finding
Experiment Name Allows Output Path Traversal
- Content
View full analysis
Vulnerability Details
File Location:
scripts/main.py:152-166
Vulnerability Type: Unrestricted output path construction
Risk Level: MediumVulnerable Code
python def save(self) -> str: """保存实验记录""" timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename_base = f"{self.experiment_name.replace(' ', '_')}_{timestamp}" if self.output_format == "markdown": file_path = self.save_directory / f"{filename_base}.md" self._save_markdown(file_path) elif self.output_format == "json": file_path = self.save_directory / f"{filename_base}.json" self._save_json(file_path) elif self.output_format == "txt": file_path = self.save_directory / f"{filename_base}.txt" self._save_text(file_path)The resulting path is subsequently opened for writing, for example:
python with open(file_path, 'w', encoding='utf-8') as f:Technical Analysis
experiment_nameis supplied through the--experimentcommand-line argument and is incorporated into the output path after replacing spaces only. Path separators, absolute paths, drive prefixes, and..traversal components are not rejected.When an absolute child path is combined with a
pathlib.Path, the absolute path takes precedence overself.save_directory. Relative traversal components can similarly escape the configured directory after path resolution. The application then opens the resulting file in write mode, creating it or truncating an existing file if its generated name already exists.The timestamp and fixed extension limit the attacker to generated filenames ending in
_<timestamp>.md,_<timestamp>.json, or_<timestamp>.txt; therefore, this does not generally permit overwriting an arbitrary exact filename. It nevertheless violates the intended output-directory boundary.Attack Path
- An attacker or untrusted caller controls the `--exper ...[truncated 1526 chars]
- Remediation
View remediation
Remediation Suggestions
- Treat the experiment name as a display value rather than a filesystem path.
- Derive a safe filename using an allowlist, such as ASCII letters, digits, hyphens, and underscores.
- Explicitly reject
/,\, absolute paths, drive prefixes, null bytes, and..path components. - Resolve both the save directory and destination, then verify containment before opening the file.
- Create files using exclusive creation where overwriting is unnecessary.
- Apply a conservative maximum filename length.
Example containment pattern:
python import re root = self.save_directory.resolve() safe_name = re.sub(r"[^A-Za-z0-9_-]", "_", self.experiment_name) safe_name = safe_name.strip("_")[:100] or "unnamed_experiment" filename = f"{safe_name}_{timestamp}.{extension}" destination = (root / filename).resolve() if root not in destination.parents: raise ValueError("Output path escapes the configured save directory")Where supported, open the destination with exclusive creation mode (
"x") to avoid silently truncating an existing output file.
