T09 · Insecure Skill Coding Practices
- Location
scripts/merge_jsonl.py:15- Finding
Input and Output Path Aliasing Can Silently Destroy JSONL Data
- Content
View full analysis
Vulnerability Details
File Location:
scripts/merge_jsonl.py, lines 15–23
Vulnerability Type: Unsafe file handling and destructive output aliasing
Risk Level: Mediumpython written = 0 with out.open('w', encoding='utf-8') as wf: for name in sorted(args.inputs): p = Path(name) if not p.exists() or not p.is_file(): continue with p.open('r', encoding='utf-8') as rf: for line in rf: if line.strip(): wf.write(line.rstrip('\n') + '\n') written += 1Technical Analysis
The destination file is opened in
wmode before the input files are opened. Opening an existing file in this mode immediately truncates it.The script does not resolve and compare each input path against the output path. Consequently, the output can also appear in the input list through:
- The same literal path
- Equivalent relative and absolute paths
- A symbolic link or another path resolving to the output
- A wildcard that includes a previously generated merged file
If this occurs, the existing output is erased before the script attempts to read it. The affected input then contributes no records to the new merged file. Writing directly to the final destination also means an interruption can leave a partially written dataset.
Attack Path
- A user, automation job, or attacker who can influence command-line arguments includes the destination file among the inputs.
- The script resolves the output path and opens it with mode
w. - Existing contents of the destination are immediately truncated.
- The merge loop later opens the same file, or an alias of it, as an input.
- The input is empty or contains only data written earlier during the same merge.
- The script exits without reporting that source records were destroyed or omitted.
- Downstream validation or training may consume an incomple ...[truncated 510 chars]
- Remediation
View remediation
Remediation Suggestions
- Resolve all input paths before opening the output and reject any input that resolves to the destination:
python out = Path(args.output).resolve() inputs = [Path(name).resolve() for name in args.inputs] if out in inputs: raise ValueError("Output path must not also be an input path") - Consider checking
Path.samefile()for existing files to detect aliases and symbolic links. - Write merged content to a securely created temporary file in the destination directory.
- Flush and synchronize the temporary file before atomically replacing the destination with
Path.replace()oros.replace(). - Treat missing input files as errors unless skipping them is explicitly requested.
- Report the number of input files and records read from each file so unexpected omissions are visible.
- Resolve all input paths before opening the output and reject any input that resolves to the destination:
