diff --git a/python-installer/techscript/downloader.py b/python-installer/techscript/downloader.py index afdba46a..9e14a0c8 100644 --- a/python-installer/techscript/downloader.py +++ b/python-installer/techscript/downloader.py @@ -104,13 +104,23 @@ def download_file(url: str, dest_path: str, show_progress: bool = True) -> None: def extract_archive(archive_path: str, dest_dir: str) -> None: """Extract .tar.gz or .zip archive to dest_dir.""" + dest_dir_real = os.path.realpath(dest_dir) + if archive_path.endswith(".tar.gz") or archive_path.endswith(".tgz"): import tarfile with tarfile.open(archive_path, "r:gz") as tf: + for member in tf.getmembers(): + member_path = os.path.realpath(os.path.join(dest_dir, member.name)) + if not os.path.commonpath([member_path, dest_dir_real]) == dest_dir_real: + raise RuntimeError(f"Attempted path traversal in archive: {member.name}") tf.extractall(dest_dir) elif archive_path.endswith(".zip"): import zipfile with zipfile.ZipFile(archive_path, "r") as zf: + for member in zf.namelist(): + member_path = os.path.realpath(os.path.join(dest_dir, member)) + if not os.path.commonpath([member_path, dest_dir_real]) == dest_dir_real: + raise RuntimeError(f"Attempted path traversal in archive: {member}") zf.extractall(dest_dir) else: raise RuntimeError(f"Unknown archive format: {archive_path}") diff --git a/tools/packager/src/main.rs b/tools/packager/src/main.rs index 85ff9ca6..5617ec15 100644 --- a/tools/packager/src/main.rs +++ b/tools/packager/src/main.rs @@ -447,7 +447,7 @@ fn zip_sub_directory(src_dir: &Path, dst_zip: &Path) -> anyhow::Result<()> { let files = walk_dir(src_dir)?; for file_path in files { - let rel_path = file_path.strip_prefix(src_dir.parent().unwrap())?; + let rel_path = file_path.strip_prefix(src_dir.parent().expect("Source directory must have a parent"))?; zip.start_file(rel_path.to_string_lossy().replace("\\", "/"), options)?; let mut f = File::open(&file_path)?; let mut buffer = Vec::new(); @@ -1262,13 +1262,13 @@ fn compile_inno_installer(iss_path: &Path, out_exe: &Path) -> anyhow::Result<()> let status = Command::new(compiler).arg(iss_path).status()?; if status.success() { - let file_name = iss_path.file_stem().unwrap().to_string_lossy(); + let file_name = iss_path.file_stem().expect("Failed to get file stem from iss_path").to_string_lossy(); let base_name = if file_name.contains("online") { "TechScript_Online_Setup.exe" } else { "TechScript_Setup.exe" }; - let generated_setup = iss_path.parent().unwrap().join(base_name); + let generated_setup = iss_path.parent().expect("Failed to get parent directory of iss_path").join(base_name); if generated_setup.exists() { fs::copy(&generated_setup, out_exe)?; @@ -1398,12 +1398,12 @@ fn generate_checksums_txt(release_dir: &Path) -> anyhow::Result<()> { let hash = hasher.finalize(); // Format to show filename relative to the release root - let filename = if file_path.parent().unwrap().ends_with("tools") { - format!("tools/{}", file_path.file_name().unwrap().to_string_lossy()) + let filename = if file_path.parent().expect("File path must have a parent").ends_with("tools") { + format!("tools/{}", file_path.file_name().expect("File path must have a file name").to_string_lossy()) } else { file_path .file_name() - .unwrap() + .expect("File path must have a file name") .to_string_lossy() .into_owned() };