T09 · Insecure Skill Coding Practices
- Location
gemini_file_runner.py:111- Finding
Arbitrary File Write Outside the Intended Output Directory
- Content
View full analysis
str: """Generate an output filename, preferring a user-provided name.""" if config.output_file is not None and config.output_file.strip() != "": return config.output_file.strip() timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") extension = "json" if config.output_format == "json" else "txt" return f"gemini-output-{timestamp}.{extension}" ``` ```python def save_output(content: str, config: RunConfig) -> Path: """Write the result to a file and return its absolute path.""" config.output_dir.mkdir(parents=True, exist_ok=True) file_name = infer_filename(config) output_path = (config.output_dir / file_name).resolve() output_path.write_text(content, encoding="utf-8") return output_path ``` ### Technical Analysis The value supplied through `--output-file` is accepted without validating that it is a simple filename or confirming that the resolved destination remains inside `config.output_dir`. A value containing parent-directory components, such as `../../target`, causes `Path.resolve()` to normalize the destination outside the intended output directory. An absolute path has an equivalent effect because joining a `Path` with an absolute second operand discards the original output directory. The subsequent `write_text()` operation creates or truncates the resolved destination without checking whether the file already exists. It can also follow a destination symlink. Consequently, a caller who controls the runner arguments can write Gemini-generated content to any location writable by the process account. ### Attack Path 1. An attacker controls or influences the arguments used to invoke the runner. 2. The attacker supplies an output path such as: `` ...[truncated 1112 chars]- Remediation
View remediation
Path: if Path(output_file).is_absolute(): raise ValueError("Absolute output paths are not allowed.") if Path(output_file).name != output_file: raise ValueError("Output file must be a simple filename.") base = output_dir.resolve() destination = (base / output_file).resolve() if not destination.is_relative_to(base): raise ValueError("Output path escapes the approved directory.") if destination.is_symlink(): raise ValueError("Symlink destinations are not allowed.") return destination ``` Where supported, use exclusive file creation and fail safely when the destination already exists. ]]>
