T09 · Insecure Skill Coding Practices
- Location
- scripts/parse.py:149
- Finding
- Input and Output Path Collision Can Truncate Source Logs in parse.py## Vulnerability Details **File Location**: `scripts/parse.py:149-150, 204, 237-248` **Vulnerability Type**: Input/output path collision and unsafe file overwrite **Risk Level**: High ### Vulnerable Code ```python try: in_path = safe_path(args.input) out_path = safe_path(args.output) except ValueError as e: print(f"Error: {e}", file=sys.stderr) return 2 ``` ```python out_path.parent.mkdir(parents=True, exist_ok=True) ``` ```python if not use_buffer: delim = "\t" if fmt_out == "tsv" else "," fout = out_path.open("w", encoding="utf-8", newline="") try: if fmt_out == "jsonl": writer = None else: writer = csv.DictWriter(fout, fieldnames=fixed_header, delimiter=delim, extrasaction="ignore") writer.writeheader() for line in iter_lines(in_path): ``` ### Technical Analysis The input and output paths are independently checked only against a character allowlist. The code never verifies that they refer to different filesystem objects. Opening `out_path` with mode `"w"` truncates the target immediately. If the output path is identical to the input path, or is a symlink or alternate path resolving to the input file, the source log can be truncated before parsing completes. Lexical comparison alone would also be insufficient because paths such as `logs/app.log` and `logs/../logs/app.log`, or two symlinks, may identify the same object. The buffered execution path still overwrites the source after reading it, so the collision remains destructive even where truncation does not occur before parsing. ### Attack Path 1. An attacker or mistaken automation supplies a source log as the input. 2. The same file, a normalized alias, hard link, or symlink to that file is supplied as the output. 3. Both paths pass `safe_path()` because the function only checks characters. 4. `out_path ...[truncated 543 chars]
- Remediation
- ## Remediation Suggestions - Resolve and normalize the input and output paths before writing. - Use `os.path.samefile()` or `Path.samefile()` when both paths exist, with a safe fallback for a new output path. - Reject hard-link, symlink, and normalized-path collisions. - Write generated data to a securely created temporary file in the destination directory and atomically replace the destination only after successful completion. - Do not follow an existing output symlink unless this behavior is explicitly required and protected. - Add tests covering identical paths, relative aliases, symlink aliases, and hard links. Example defensive check: ```python resolved_input = in_path.resolve(strict=True) resolved_output = out_path.resolve(strict=False) if resolved_input == resolved_output: raise ValueError("Input and output must refer to different files") if out_path.exists() and in_path.samefile(out_path): raise ValueError("Input and output must refer to different files") ```
