T09 · Insecure Skill Coding Practices
- Location
scripts/batch_rename.py:44- Finding
Unrestricted Rename Destinations Permit Directory Escape and File Overwrites
- Content
View full analysis
Vulnerability Details
File Location:
scripts/batch_rename.py, lines 44-65 and 95-110
Vulnerability Type: Unvalidated filesystem destination and unsafe file replacement
Risk Level: MediumVulnerable Code
python new_name = f"{prefix}{name}{suffix}{ext}" print(f"{filename:30s} → {new_name}") rename_list.append((filename, new_name)) success = 0 for old_name, new_name in rename_list: try: old_path = os.path.join(folder_path, old_name) new_path = os.path.join(folder_path, new_name) os.rename(old_path, new_path) success += 1 except Exception as e: print(f"Rename of {old_name} failed: {e}")The numbering mode performs the same unsafe destination operation:
python new_name = f"{i:03d}_{name}{ext}" rename_list.append((filename, new_name)) for old_name, new_name in rename_list: old_path = os.path.join(folder_path, old_name) new_path = os.path.join(folder_path, new_name) os.rename(old_path, new_path)Technical Analysis
The prefix and suffix used to construct
new_namecome directly from interactive input. The program does not require the resulting value to remain a single filename component. In particular, a prefix can contain path separators and parent-directory components such as../.os.path.join(folder_path, new_name)does not enforce containment withinfolder_path. For example, a prefix of../target/can produce a destination equivalent to:text selected-folder/../target/original-file.txtThe normalized destination is outside the directory selected for batch renaming.
The program also does not check whether:
- A destination already exists.
- Two source files resolve to the same destination.
- A destination conflicts with another source in the rename batch.
- The resolved destination remains inside the selected directory.
- A failure leaves the operation only part ...[truncated 2317 chars]
- Remediation
View remediation
Remediation Suggestions
-
Restrict prefix and suffix values to filename-safe text. Reject path separators, parent-directory components, NUL bytes, and platform-specific separators:
python def validate_affix(value): if "\x00" in value or value in {".", ".."}: raise ValueError("Invalid filename component") if any(separator and separator in value for separator in (os.sep, os.altsep)): raise ValueError("Path separators are not allowed") -
Resolve and enforce destination containment. Verify that every destination has the selected directory as its direct parent:
python base = os.path.realpath(folder_path) destination = os.path.realpath(os.path.join(base, new_name)) if os.path.dirname(destination) != base: raise ValueError("Destination escapes the selected directory") -
Reject existing destinations. Before changing any files, check every planned destination with
os.path.lexists(). Abort the entire operation if a destination already exists and is not the same source path. -
Detect duplicate and cross-operation destinations. Normalize all destinations and verify that they are unique. Also detect cases where one generated destination is another source file in the same batch.
-
Use a two-phase rename strategy. First rename each source to a unique temporary name inside the same directory. After every first-phase operation succeeds, rename the temporary files to their final validated destinations. If either phase fails, attempt rollback.
-
Validate the selected path as a directory. Replace the existence-only check with:
python if not os.path.isdir(folder_path): raise ValueError("The selected path must be a directory") -
Display normalized absolute destinations in the preview. This makes directory escape or unexpected path resolut ...[truncated 241 chars]
-
