@@ -90,6 +90,32 @@ def _fetch_content(
9090 progress .close ()
9191
9292
93+ def _safe_zip_file_extractall (zip_file : zipfile .ZipFile , path : str ) -> None :
94+ """Extracts all members from a zip file to a directory, safely.
95+
96+ This function is a safer alternative to `zipfile.ZipFile.extractall` as it
97+ prevents directory traversal exploits by ensuring that all extracted files
98+ are within the specified `path`.
99+
100+ Args:
101+ zip_file: The ZipFile object to extract from.
102+ path: The destination directory.
103+
104+ Raises:
105+ ValueError: If a member's name attempts to traverse outside the `path`.
106+ """
107+ for info in zip_file .infolist ():
108+ # Construct the full path where the member would be extracted.
109+ extracted_path = os .path .join (path , info .filename )
110+ # Ensure the constructed path is still within the designated 'path'.
111+ # os.path.realpath is used to resolve any '..' components.
112+ real_path = os .path .realpath (extracted_path )
113+ base_path = os .path .realpath (path )
114+ if os .path .commonpath ([base_path , real_path ]) != base_path :
115+ raise ValueError (f'unsafe path encountered in zip file: { info .filename } ' )
116+ zip_file .extract (info , path )
117+
118+
93119def _get_extacted_dir (output_path : str ) -> str :
94120 """Extracts and returns the directory containing the provided archive."""
95121 is_zip = zipfile .is_zipfile (output_path )
@@ -110,7 +136,7 @@ def _get_extacted_dir(output_path: str) -> str:
110136
111137 if is_zip :
112138 with zipfile .ZipFile (output_path , 'r' ) as zip_file :
113- zip_file . extractall ( output_extracted_path )
139+ _safe_zip_file_extractall ( zip_file , output_extracted_path )
114140 zip_file .close ()
115141 else :
116142 tar_file = tarfile .open (output_path )
0 commit comments