T09 · Insecure Skill Coding Practices
- Location
scripts/find_stl.py:187- Finding
Path Traversal Through Untrusted Remote Model Filename
- Content
View full analysis
Vulnerability Details
File Location:
scripts/find_stl.py, lines 187-191; file-write sink at lines 111-120
Vulnerability Type: Path traversal leading to arbitrary file creation or overwrite
Risk Level: HighVulnerable Code
python for fobj in stls: fid = str(fobj["id"]) name = fobj.get("name") or f"file-{fid}" link = printables_get_download_link(p["id"], "stl", [fid]) out_path = os.path.join(base_dir, "files", name) download_file(link, out_path)The resulting path reaches the following file-write sink:
python def download_file(url: str, out_path: str, timeout: int = 60) -> None: os.makedirs(os.path.dirname(out_path), exist_ok=True) req = urllib.request.Request(url, headers={"user-agent": "Mozilla/5.0"}) with urllib.request.urlopen(req, timeout=timeout) as r: with open(out_path, "wb") as f: while True: b = r.read(1024 * 1024) if not b: break f.write(b)Technical Analysis
The
namevalue is obtained from remotely supplied Printables model metadata and is passed directly toos.path.join()without filename sanitization, path normalization, or destination containment validation.os.path.join(base_dir, "files", name)does not guarantee that the result remains inside the intended download directory. A filename containing parent-directory components such as../../targetcan escape that directory. On supported platforms, an absolute filename can also cause the preceding path components to be discarded.The calculated path is passed to
download_file(), which creates parent directories and opens the destination inwbmode. Consequently, an escaped destination is created or overwritten without confirmation. Thesafe_slug()protection applied to the model directory does not protect individual remote filenames.Attack Path
- An attac ...[truncated 1628 chars]
- Remediation
View remediation
Remediation Suggestions
- Treat every remote filename as untrusted. Reject absolute paths, parent-directory components, path separators, NUL characters, and platform-specific drive or UNC path syntax.
- Reduce the remote value to a safe filename using a strict allowlist or a sanitized basename. Generate a local filename from the trusted file ID when the supplied name is invalid.
- Resolve both the destination root and candidate path to canonical absolute paths, then verify containment before creating directories or opening the file.
- Refuse duplicate destinations and existing files by default. Require an explicit overwrite option if replacement is intended.
- Open newly downloaded files with exclusive creation mode where practical to reduce unintended overwrites and race conditions.
- Add tests covering
../, nested traversal, absolute paths, Windows drive paths, UNC paths, mixed separators, empty names, and duplicate sanitized names.
Example containment pattern:
python files_dir = os.path.realpath(os.path.join(base_dir, "files")) os.makedirs(files_dir, exist_ok=True) remote_name = fobj.get("name") or f"file-{fid}" safe_name = os.path.basename(remote_name.replace("\\", "/")) if not safe_name or safe_name in {".", ".."}: safe_name = f"file-{fid}" out_path = os.path.realpath(os.path.join(files_dir, safe_name)) if os.path.commonpath([files_dir, out_path]) != files_dir: raise RuntimeError(f"Unsafe remote filename: {remote_name!r}") if os.path.exists(out_path): raise RuntimeError(f"Refusing to overwrite existing file: {out_path}") download_file(link, out_path)For stronger isolation, derive the local filename entirely from the trusted file ID and retain the original remote filename only as metadata in
manifest.json.
