@@ -173,11 +173,15 @@ def safe_extract_tarball(
173173 # Tar metadata member types to skip during validation — they carry no
174174 # extractable payload and are generated automatically by many common
175175 # archiving tools (e.g. PAX headers, GNU longname/longlink entries).
176+ # GNUTYPE_SPARSE is intentionally excluded: it carries a real file payload
177+ # and tarfile.TarInfo.isreg() returns True for it, so it passes the
178+ # regular-file check below and is extracted correctly.
176179 _TAR_METADATA_TYPES = (
177- tarfile .XHDTYPE , # PAX extended header
178- tarfile .XGLTYPE , # PAX global extended header
179- tarfile .SOLARIS_XHDTYPE , # Solaris PAX extended header
180- * tarfile .GNU_TYPES , # GNU longname / longlink / sparse
180+ tarfile .XHDTYPE , # PAX extended header
181+ tarfile .XGLTYPE , # PAX global extended header
182+ tarfile .SOLARIS_XHDTYPE , # Solaris PAX extended header
183+ tarfile .GNUTYPE_LONGNAME , # GNU long path name (metadata only)
184+ tarfile .GNUTYPE_LONGLINK , # GNU long link name (metadata only)
181185 )
182186
183187 try :
@@ -2153,21 +2157,34 @@ def download_extension(self, extension_id: str, target_dir: Optional[Path] = Non
21532157 version = ext_info .get ("version" , "unknown" )
21542158
21552159 # Detect archive format from URL; resolve via Content-Type when needed.
2160+ # `final_url` may differ from `download_url` if the server redirects.
21562161 archive_fmt = detect_archive_format (download_url )
2162+ final_url = download_url
21572163
21582164 # Download the archive
21592165 try :
21602166 with self ._open_url (download_url , timeout = 60 ) as response :
2167+ final_url = response .geturl ()
21612168 if not archive_fmt :
21622169 content_type = response .headers .get ("Content-Type" , "" )
2163- archive_fmt = detect_archive_format (download_url , content_type )
2170+ archive_fmt = detect_archive_format (final_url , content_type )
21642171 archive_data = response .read ()
21652172
21662173 except urllib .error .URLError as e :
21672174 raise ExtensionError (f"Failed to download extension from { download_url } : { e } " )
21682175 except IOError as e :
21692176 raise ExtensionError (f"Failed to read extension archive from { download_url } : { e } " )
21702177
2178+ # Re-validate scheme after any redirect to guard against scheme-downgrade.
2179+ _final_parsed = urlparse (final_url )
2180+ _final_is_localhost = _final_parsed .hostname in ("localhost" , "127.0.0.1" , "::1" )
2181+ if _final_parsed .scheme != "https" and not (
2182+ _final_parsed .scheme == "http" and _final_is_localhost
2183+ ):
2184+ raise ExtensionError (
2185+ f"Extension download URL was redirected to a non-HTTPS URL: { final_url } "
2186+ )
2187+
21712188 # Choose file extension based on detected format.
21722189 if not archive_fmt :
21732190 raise ExtensionError (
0 commit comments