T09 · Insecure Skill Coding Practices
- Location
code_cache.py:91- Finding
Remote-Controlled Filename Allows Arbitrary File Write Outside the Cache Directory
- Content
View full analysis
Vulnerability Details
File Location:
code_cache.py, lines 91–102
Vulnerability Type: Path traversal and arbitrary file overwrite
Risk Level: Highpython result = client.get_code_files( task=task, top_k=args.top_k, cache_dir=args.cache_dir, ) if not result.files: print(f"No code files found for: {task}") return print(f"Retrieved {len(result.files)} files for: {task}\n") # Write files to cache directory cache_path = Path(args.cache_dir) cache_path.mkdir(parents=True, exist_ok=True) for f in result.files: file_path = cache_path / f.filename file_path.write_text(f.source)Technical Analysis
The filename and source content originate from the external Raysurfer API. The implementation joins
cache_pathwithf.filenamewithout validating whether the filename is absolute or contains parent-directory components.In Python, joining a
Pathto an absolute child discards the original base path. A relative filename containing components such as../../can similarly resolve outside the intended cache directory.write_text()also overwrites an existing file by default.Consequently, the external service controls both the destination path and the content written to it. The code does not resolve the final path and verify that it remains beneath the selected cache directory.
Attack Path
- An attacker poisons a cache entry, compromises an account, or compromises the external cache service.
- The malicious response contains a filename such as
../../project/config.py,../../.bashrc, or an absolute path. - A user invokes the
filescommand for a task matching the poisoned entry. - The Skill joins the attacker-controlled filename to the local cache path.
write_text()writes attacker-controlled source outside the cache directory and overwrites the target if it already exists.- If the overwritten file is later imported, executed, or ...[truncated 606 chars]
- Remediation
View remediation
Remediation Suggestions
- Treat every filename returned by the API as untrusted.
- Reject absolute paths, parent-directory components, empty names, device names, and unexpected separators.
- Resolve the destination path and verify that it remains strictly beneath the resolved cache directory:
python cache_path = Path(args.cache_dir).resolve() cache_path.mkdir(parents=True, exist_ok=True) for remote_file in result.files: relative_name = Path(remote_file.filename) if relative_name.is_absolute() or ".." in relative_name.parts: raise ValueError(f"Unsafe remote filename: {remote_file.filename}") destination = (cache_path / relative_name).resolve() if cache_path not in destination.parents: raise ValueError(f"Filename escapes cache directory: {remote_file.filename}") destination.parent.mkdir(parents=True, exist_ok=True) destination.write_text(remote_file.source)- Prefer a strict filename allowlist if nested directories are unnecessary.
- Avoid silently overwriting existing files. Use exclusive creation or require explicit confirmation.
- Add tests covering absolute paths,
../traversal, nested traversal, symbolic links, and overwrite attempts.
