T09 · Insecure Skill Coding Practices
- Location
scripts/imagegen.py:350- Finding
Server-Controlled Output Filename Allows Path Traversal
- Content
View full analysis
Vulnerability Details
File Location:
scripts/imagegen.py:350-364; file-write sink atscripts/imagegen.py:263-283
Vulnerability Type: Path traversal and arbitrary file overwrite
Risk Level: MediumVulnerable Code
python task_type = task.get("taskType", "task") for i, item in enumerate(succeeded): result_url = item.get("resultUrl") if not result_url: continue ext = ".mp4" if "video" in task_type.lower() else ".png" filename = f"{task_type}_{task_id}_{i+1}{ext}" out_path = out_dir / filename download_file(result_url, out_path) for item in succeeded: last_frame = item.get("lastFrameUrl") if last_frame: out_path = out_dir / f"{task_type}_{task_id}_lastframe.png" download_file(last_frame, out_path)The resulting path is subsequently opened for writing:
python def download_file(url, out_path, timeout=300): try: resp = requests.get(url, stream=True, timeout=timeout) resp.raise_for_status() total = int(resp.headers.get("content-length", 0)) downloaded = 0 with open(str(out_path), "wb") as f: for chunk in resp.iter_content(chunk_size=8192): if chunk: f.write(chunk)Technical Analysis
The
taskTypefield is obtained from the remote task response and is incorporated directly into a local filename without validation, sanitization, or canonical-path containment checks. The server-issuedtask_idis also incorporated into the filename.A crafted value containing path components such as
../can cause Python'sPathjoining operation to resolve the output outside the intendedout_dir. The resulting path is passed toopen(..., "wb"), which creates a new file or truncates an existing file when the current user has sufficient filesystem permissions.TLS reduces the likelihood of ordinary network interception, but it does not protect against a compromised service, malicious upstr ...[truncated 1555 chars]
- Remediation
View remediation
Remediation Suggestions
Do not derive local filenames directly from remote response metadata.
- Generate filenames from locally controlled constants and counters, for example:
python filename = f"image_{i + 1}.png"- If remote identifiers must appear in filenames, restrict each identifier to a conservative allowlist:
python import re def safe_component(value, fallback): value = str(value) if not re.fullmatch(r"[A-Za-z0-9_-]{1,100}", value): return fallback return value- Resolve and verify the final path before writing:
python base = Path(output_dir).expanduser().resolve() candidate = (base / filename).resolve() if candidate.parent != base: raise SystemExit("Refusing to write outside the output directory")For subdirectories, use
candidate.is_relative_to(base)on supported Python versions instead of requiring the direct parent to equal the base.-
Reject absolute paths, path separators,
.components, and..components in every remote value used in a filename. -
Consider exclusive file creation or explicit overwrite confirmation to prevent silent truncation of existing files.
-
Apply the same containment validation to normal result downloads and last-frame downloads.
