T09 · Insecure Skill Coding Practices
- Location
scripts/router9.py:201- Finding
Server-Controlled Download Filename Allows Arbitrary File Overwrite
- Content
View full analysis
Vulnerability Details
File Location:
scripts/router9.py, lines 201–205
Vulnerability Type: Unvalidated server-controlled file path
Risk Level: Highpython download_url = result["data"]["url"] filename = result["data"].get("filename", "download") # Step 2: Download from S3 output = args.output or filename urllib.request.urlretrieve(download_url, output)Technical Analysis
When the user does not provide the
--outputoption, the script uses a filename returned by the Router9 API as the local download destination. The value is passed directly tourllib.request.urlretrievewithout validating whether it is an absolute path, contains parent-directory components such as.., or resolves outside an intended download directory.Consequently, a malicious or compromised API response could provide a filename such as
../../.ssh/configor an absolute path. The downloaded content would then be written to that location if it is writable by the process. Merely applyingPath.resolve()would not be sufficient; the resolved path must also be checked against a trusted destination directory.The vulnerability does not elevate the process to operating-system administrator privileges. Its scope is limited to files writable by the user or service account running the skill, but those files may include application source, configuration, credentials, shell initialization files, or other security-sensitive resources.
Attack Path
- A malicious or compromised Router9 API response returns crafted download metadata containing an absolute filename or directory-traversal sequence.
- Alternatively, attacker-controlled storage metadata could create the same condition if the service preserves unsafe filenames.
- A user or agent invokes
python scripts/router9.py download <file_id>without explicitly supplying--output. - The script assigns the untrusted API filename to
output. urllib.request.urlretrievewrites the remote content to ...[truncated 974 chars]
- Remediation
View remediation
Remediation Suggestions
- Never use a remote filename directly as a filesystem path. Reduce it to a safe basename and reject empty or special names:
python from pathlib import Path raw_filename = result["data"].get("filename", "download") safe_filename = Path(raw_filename).name if safe_filename in {"", ".", ".."}: raise ValueError("Invalid download filename")- Place implicit downloads in a designated trusted directory, resolve the final path, and verify containment:
python download_dir = Path.cwd() / "downloads" download_dir.mkdir(parents=True, exist_ok=True) download_root = download_dir.resolve() destination = (download_root / safe_filename).resolve() if destination.parent != download_root: raise ValueError("Download path escapes the destination directory")-
Treat an explicit
--outputpath separately. If arbitrary destinations are intended, clearly document that behavior and require explicit user authorization before overwriting an existing file. -
Use exclusive file creation or refuse overwrites by default. Provide a separate
--forceoption when replacement is intentionally requested. -
Consider downloading to a securely created temporary file in the destination directory and atomically renaming it after the transfer completes successfully.
-
Add tests covering absolute paths,
../traversal, nested traversal, platform-specific path separators, empty filenames, symbolic-link destinations, and attempts to overwrite existing files.
