T09 · Insecure Skill Coding Practices
- Location
scripts/generate_image.py:284- Finding
Path Traversal Through Server-Controlled Image Filename
- Content
View full analysis
Vulnerability Details
File Location:
scripts/generate_image.py, lines 218–229 and 284–290
Vulnerability Type: Path traversal and arbitrary file placement
Risk Level: MediumVulnerable Code
python def download_image(url: str, filename: str, output_dir: Path) -> Path: """Download an image to the specified directory.""" output_path = output_dir / filename print(f"\n📥 Downloading image...") print(f" URL: {url}") print(f" Saving to: {output_path}") try: output_dir.mkdir(parents=True, exist_ok=True) request.urlretrieve(url, output_path)The filename passed to this function is derived directly from a remote API response:
python for img_data in generated_urls: url = img_data.get("url") name = img_data.get("name") if not url or not name: print(f"⚠️ Skipping invalid data: {img_data}") continue filename = f"{name}.png" path = download_image(url, filename, output_dir)Technical Analysis
The
namefield is controlled by the remote image-generation service and is concatenated with.pngwithout validation. The resulting value is joined tooutput_dirusing:python output_path = output_dir / filenamePython's
pathlibdoes not automatically restrict the resulting path to the intended directory. A name containing parent-directory components, such as../../target, can resolve outsideqingping-ai/. An absolute name can also cause the original output directory to be discarded.The
.pngsuffix limits the destination to a filename ending in.png, but it does not prevent overwriting existing image files or placing attacker-controlled content at arbitrary writable paths with that suffix.This behavior exceeds the minimum filesystem privileges required by the Skill. Generated files only need to be written beneath the documented
qingping-ai/directory.Attack
...[truncated 1206 chars]
- Remediation
View remediation
Remediation Suggestions
Treat every filename returned by the API as untrusted.
- Prefer generating filenames locally with a UUID or other cryptographically random identifier.
- If the remote name must be retained, reject absolute paths, parent-directory components, path separators, null bytes, and platform-specific reserved names.
- Resolve both the output directory and destination path and verify containment before writing.
- Open new files using exclusive creation where overwriting is not required.
- Apply a conservative filename-length limit.
Example hardening:
python import re import uuid output_root = output_dir.resolve() safe_name = re.sub(r"[^A-Za-z0-9._-]", "_", Path(name).name) safe_name = safe_name[:100] or uuid.uuid4().hex output_path = (output_root / f"{safe_name}.png").resolve() if output_root not in output_path.parents: raise ValueError("Invalid output filename")For stronger isolation, ignore the remote name entirely:
python output_path = output_dir.resolve() / f"{uuid.uuid4().hex}.png"
