T09 · Insecure Skill Coding Practices
- Location
scripts/split_files.py:116- Finding
Predictable Output Files Permit Overwrite and Symbolic-Link Attacks
- Content
View full analysis
Vulnerability Details
File Location:
scripts/split_files.py, lines 116–123 and 186–194
Vulnerability Type: Unsafe file creation and symbolic-link following
Risk Level: MediumVulnerable Code
JSON output path:
python seq = str(idx + 1).zfill(seq_digits) out_name = f"{base}{seq}{ext}" out_path = os.path.join(output_folder, out_name) if dry_run: print(f" [DRY] {out_name} ({len(chunk)} 条)") else: with open(out_path, "w", encoding="utf-8") as f: json.dump(chunk, f, ensure_ascii=False, indent=2)Markdown and text output path:
python seq = str(idx + 1).zfill(seq_digits) out_name = f"{base}{seq}{ext}" out_path = os.path.join(output_folder, out_name) chunk_bytes = len(chunk.encode("utf-8")) if dry_run: print(f" [DRY] {out_name} ({fmt_size(chunk_bytes)})") else: with open(out_path, "w", encoding="utf-8") as f: f.write(chunk)Technical Analysis
The output filenames are deterministically derived from the source filename and a sequential number. The script opens each destination using Python's
"w"mode without first ensuring that the path does not exist and is not a symbolic link.The
"w"mode silently truncates an existing regular file. It also follows symbolic links under normal filesystem semantics. Consequently, a party able to create entries in the selected output directory can predict the generated filename and place a symbolic link there before execution. When the script opens that path, it writes generated chunk content to the symlink target.The destination path is joined to the requested output folder, but no canonical-path or file-type validation is performed immediately before creation. There is also no use of exclusive creation, no atomic no-follow operation, and no explicit overwrite confirmation.
Attack Path
- The attacker obtains write access to an output directory that a victim will use with the skill.
- The attacker learns or predicts the source basename and for ...[truncated 1278 chars]
- Remediation
View remediation
Remediation Suggestions
- Refuse to overwrite existing destinations by default. Use exclusive creation:
python with open(out_path, "x", encoding="utf-8") as f: json.dump(chunk, f, ensure_ascii=False, indent=2)-
Add an explicit
--overwriteoption if replacement is required. Keep safe, non-overwriting behavior as the default and clearly warn users before replacement. -
Reject symbolic links and other unexpected file types. Where supported, open files using low-level flags such as
O_CREAT | O_EXCL | O_WRONLY | O_NOFOLLOW, then wrap the resulting descriptor withos.fdopen. -
Resolve and validate the destination path against a canonical output directory before writing. This should supplement, not replace, no-follow and exclusive-creation controls because path checks alone can be vulnerable to time-of-check/time-of-use races.
-
For intended replacement operations, write to a securely and exclusively created temporary file in the same directory, flush and synchronize it as appropriate, and then atomically replace the final destination only after validating overwrite policy.
-
Ensure the output directory has restrictive permissions and is not writable by untrusted users. Document that shared or attacker-controlled output directories are unsafe without these protections.
