T09 · Insecure Skill Coding Practices
- Location
scripts/ffdec_game_decompile.py:108- Finding
Arbitrary File Truncation Through Unrestricted Dump Output Path
- Content
View full analysis
Vulnerability Details
File Location:
scripts/ffdec_game_decompile.py:108-120andscripts/ffdec_game_decompile.py:231-235
Vulnerability Type: Unrestricted file overwrite and truncation
Risk Level: MediumVulnerable Code
python def run(cmd: list[str], dry_run: bool = False, stdout_path: Path | None = None) -> int: printable = " ".join(f'"{c}"' if " " in str(c) else str(c) for c in cmd) print(printable) if dry_run: return 0 if stdout_path: stdout_path.parent.mkdir(parents=True, exist_ok=True) with stdout_path.open("w", encoding="utf-8", errors="replace") as fh: proc = subprocess.run(cmd, stdout=fh, stderr=subprocess.STDOUT, text=True) return proc.returncode proc = subprocess.run(cmd) return proc.returncodeThe affected
dumpcall site passes a user-controlled path directly to this function:python def dump_command(args) -> int: ffdec = str(find_ffdec(args.ffdec)) swf = str(Path(args.swf).resolve()) out = str(Path(args.output).resolve()) return run([ffdec, "-dumpSWF", swf], args.dry_run, Path(out))Technical Analysis
The positional
outputargument is fully controlled by the caller. Although it is converted to an absolute path, no validation ensures that it differs from the source SWF, points to an intended workspace, or does not identify an existing sensitive file.The
run()function opens this path using Python's"w"mode before FFDec is started. This mode creates a missing file or immediately truncates an existing file to zero bytes. Consequently, successful FFDec processing is not required for data loss to occur.This behavior also conflicts with the safety guarantee in
SKILL.mdthat the original SWF will never be overwritten. Path resolution alone does not enforce that guarantee. There is no no-clobber policy, source/output identity check, confirmation prompt, or ...[truncated 1590 chars]- Remediation
View remediation
Remediation Suggestions
-
Reject identical source and output files before opening the destination. Resolve both paths and use
Path.samefile()when both exist, with a normalized-path comparison as a fallback:python swf_path = Path(args.swf).resolve() output_path = Path(args.output).resolve() if output_path == swf_path: raise ValueError("The output path must not be the source SWF.") if output_path.exists() and swf_path.exists() and output_path.samefile(swf_path): raise ValueError("The output path identifies the source SWF.") -
Refuse to overwrite existing output files by default. Add an explicit
--forceoption if replacement is required, and clearly warn that it permits destructive behavior. -
For no-clobber creation, use exclusive creation mode (
"x") rather than"w"when the destination must not already exist. -
Capture FFDec output in a temporary file located in the destination directory. Only after FFDec exits successfully should the application atomically rename the temporary file to the requested destination.
-
Remove the temporary file after subprocess failure and leave any existing destination untouched.
-
Apply equivalent safeguards to predictable files generated by the
fullworkflow. Require a new or empty output directory unless an explicit overwrite option is provided. -
Add automated tests covering:
- Source and output paths that are textually identical.
- Different paths that resolve to the same file through symbolic links.
- Existing output files without
--force. - FFDec failure without modification of an existing destination.
- Successful atomic replacement when overwrite is explicitly authorized.
-
