@@ -171,50 +171,55 @@ def _safe_extract_tarball(
171171 """
172172 dest_resolved = dest_dir .resolve ()
173173
174- with tarfile .open (archive_path , "r:gz" ) as tf :
175- members = tf .getmembers ()
176- safe_members = []
177-
178- # Validate every member before extracting anything.
179- for member in members :
180- # Reject absolute paths and any path component that is "..".
181- if os .path .isabs (member .name ) or any (
182- part == ".." for part in member .name .replace ("\\ " , "/" ).split ("/" )
183- ):
184- raise error_class (
185- f"Unsafe path in tar archive: { member .name } (potential path traversal)"
186- )
174+ try :
175+ with tarfile .open (archive_path , "r:gz" ) as tf :
176+ members = tf .getmembers ()
177+ safe_members = []
178+
179+ # Validate every member before extracting anything.
180+ for member in members :
181+ # Reject absolute paths and any path component that is "..".
182+ if os .path .isabs (member .name ) or any (
183+ part == ".." for part in member .name .replace ("\\ " , "/" ).split ("/" )
184+ ):
185+ raise error_class (
186+ f"Unsafe path in tar archive: { member .name } (potential path traversal)"
187+ )
187188
188- # Confirm the resolved path stays inside dest_dir.
189- member_path = (dest_dir / member .name ).resolve ()
190- try :
191- member_path .relative_to (dest_resolved )
192- except ValueError :
193- raise error_class (
194- f"Unsafe path in tar archive: { member .name } (potential path traversal)"
195- )
189+ # Confirm the resolved path stays inside dest_dir.
190+ member_path = (dest_dir / member .name ).resolve ()
191+ try :
192+ member_path .relative_to (dest_resolved )
193+ except ValueError :
194+ raise error_class (
195+ f"Unsafe path in tar archive: { member .name } (potential path traversal)"
196+ )
196197
197- # Reject symlinks and hard links.
198- if member .issym () or member .islnk ():
199- raise error_class (
200- f"Symlinks are not allowed in archive: { member .name } "
201- )
198+ # Reject symlinks and hard links.
199+ if member .issym () or member .islnk ():
200+ raise error_class (
201+ f"Symlinks are not allowed in archive: { member .name } "
202+ )
202203
203- # Only allow regular files and directories.
204- if not (member .isreg () or member .isdir ()):
205- raise error_class (
206- f"Non-regular file in archive: { member .name } "
207- )
204+ # Only allow regular files and directories.
205+ if not (member .isreg () or member .isdir ()):
206+ raise error_class (
207+ f"Non-regular file in archive: { member .name } "
208+ )
208209
209- safe_members .append (member )
210+ safe_members .append (member )
210211
211- # Extract — use the "data" filter on Python 3.12+ for extra hardening.
212- # On older versions pass only the pre-validated members so that no
213- # unvetted entry (added concurrently or via a race) slips through.
214- if sys .version_info >= (3 , 12 ):
215- tf .extractall (dest_dir , filter = "data" ) # type: ignore[call-arg]
216- else :
217- tf .extractall (dest_dir , members = safe_members ) # noqa: S202 — validated above
212+ # Extract — use the "data" filter on Python 3.12+ for extra hardening.
213+ # On older versions pass only the pre-validated members so that no
214+ # unvetted entry (added concurrently or via a race) slips through.
215+ if sys .version_info >= (3 , 12 ):
216+ tf .extractall (dest_dir , filter = "data" ) # type: ignore[call-arg]
217+ else :
218+ tf .extractall (dest_dir , members = safe_members ) # noqa: S202 — validated above
219+ except error_class :
220+ raise
221+ except (tarfile .TarError , OSError ) as e :
222+ raise error_class (f"Failed to read archive { archive_path } : { e } " ) from e
218223
219224
220225@dataclass
0 commit comments