T09 · Insecure Skill Coding Practices
- Location
tool.py:20- Finding
Destination Symlink Allows Files to Be Moved Outside the Selected Directory
- Content
View full analysis
Vulnerability Details
File Location:
tool.py, lines 20–36
Vulnerability Type: Symlink following and time-of-check to time-of-use race
Risk Level: Mediumpython if item.is_file(): ext = item.suffix[1:].lower() or "no_ext" extensions.add(ext) target_dir = directory / ext target_dir.mkdir(exist_ok=True) target_path = target_dir / item.name # Handle filename conflicts counter = 1 while target_path.exists(): name_stem = item.stem name_suffix = item.suffix target_path = target_dir / f"{name_stem}_{counter}{name_suffix}" counter += 1 shutil.move(str(item), str(target_path))Technical Analysis
The extension-based destination is constructed beneath the selected directory, but the code does not verify that
target_diris a genuine directory rather than a symbolic link. When an existing symbolic link resolves to a directory,target_dir.mkdir(exist_ok=True)can accept it because the path is considered an existing directory after following the link.Consequently,
shutil.move()follows the symbolic link and places the source file in the link's target, which may be outside the directory the user selected. The destination existence check and subsequent move are also separate filesystem operations, creating a time-of-check to time-of-use window. An attacker with concurrent write access to the selected directory could replace a destination directory or path after validation or collision checking but before the move.Attack Path
- An attacker obtains write access to a directory that the victim will organize, such as a shared directory.
- The attacker creates an extension-named symbolic link, for example
pdf, pointing to an attacker-chosen directory outside the selected directory. - The victim places a file such as
confidential.pdfin the directory and runs the sorting tool. - The tool derives
pdfas the ext ...[truncated 954 chars]
- Remediation
View remediation
Remediation Suggestions
- Reject a destination when
target_dir.is_symlink()is true. - Resolve every destination and verify that it remains beneath the resolved source directory before moving a file.
- Apply containment checks using
Path.relative_to()rather than string-prefix comparisons. - Revalidate the destination immediately before each move.
- Where supported, use descriptor-relative filesystem operations and flags that prohibit symbolic-link traversal to reduce race conditions.
- Avoid processing directories writable by untrusted users, particularly when the tool runs with elevated privileges.
- Refuse symbolic links as source entries unless following them is an explicitly documented requirement.
- Add tests covering pre-existing destination symlinks, symlink replacement during processing, filename-collision races, and destinations outside the selected directory.
- Reject a destination when
