Your message dated Sat, 11 Jul 2026 10:32:47 +0000
with message-id <[email protected]>
and subject line Released in 13.6
has caused the Debian Bug report #1139573,
regarding trixie-pu: package gdown/5.2.0+dfsg-2+deb13u1
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact [email protected]
immediately.)


-- 
1139573: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1139573
Debian Bug Tracking System
Contact [email protected] with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
Tags: trixie
X-Debbugs-Cc: [email protected], [email protected]
Control: affects -1 + src:gdown
User: [email protected]
Usertags: pu

  * CVE-2026-40491: Arbitrary File Write via Path Traversal
diffstat for gdown-5.2.0+dfsg gdown-5.2.0+dfsg

 changelog                                                               |    7 
 patches/0001-fix-prevent-path-traversal-in-archive-extraction-and.patch |  195 
++++++++++
 patches/series                                                          |    1 
 3 files changed, 203 insertions(+)

diff -Nru gdown-5.2.0+dfsg/debian/changelog gdown-5.2.0+dfsg/debian/changelog
--- gdown-5.2.0+dfsg/debian/changelog   2025-01-10 21:46:54.000000000 +0200
+++ gdown-5.2.0+dfsg/debian/changelog   2026-06-09 23:11:29.000000000 +0300
@@ -1,3 +1,10 @@
+gdown (5.2.0+dfsg-2+deb13u1) trixie; urgency=medium
+
+  * Non-maintainer upload.
+  * CVE-2026-40491: Arbitrary File Write via Path Traversal
+
+ -- Adrian Bunk <[email protected]>  Tue, 09 Jun 2026 23:11:29 +0300
+
 gdown (5.2.0+dfsg-2) unstable; urgency=medium
 
   * Source-only upload to allow package to migrate to testing.
diff -Nru 
gdown-5.2.0+dfsg/debian/patches/0001-fix-prevent-path-traversal-in-archive-extraction-and.patch
 
gdown-5.2.0+dfsg/debian/patches/0001-fix-prevent-path-traversal-in-archive-extraction-and.patch
--- 
gdown-5.2.0+dfsg/debian/patches/0001-fix-prevent-path-traversal-in-archive-extraction-and.patch
     1970-01-01 02:00:00.000000000 +0200
+++ 
gdown-5.2.0+dfsg/debian/patches/0001-fix-prevent-path-traversal-in-archive-extraction-and.patch
     2026-06-09 23:10:34.000000000 +0300
@@ -0,0 +1,195 @@
+From 62da8ca8de8faef7c875b1a221413243e96737b8 Mon Sep 17 00:00:00 2001
+From: Kentaro Wada <[email protected]>
+Date: Sun, 12 Apr 2026 14:47:54 +0900
+Subject: fix: prevent path traversal in archive extraction and filename
+ handling
+
+- Add path validation to extractall() to prevent zip/tar slip attacks
+- Add _sanitize_filename() to neutralize path separators, null bytes,
+  and dot-dot sequences in filenames from responses and URLs
+- Sanitize root folder name in download_folder() before building paths
+- Use Python 3.12+ data filter for tar extraction when available,
+  with manual link/special-file/traversal checks for older versions
+
+Fixes GHSA-76hw-p97h-883f
+---
+ gdown/download.py        | 16 ++++++---
+ gdown/download_folder.py |  5 ++-
+ gdown/extractall.py      | 72 ++++++++++++++++++++++++++++++----------
+ 3 files changed, 70 insertions(+), 23 deletions(-)
+
+diff --git a/gdown/download.py b/gdown/download.py
+index baadef1..4ba6c6d 100644
+--- a/gdown/download.py
++++ b/gdown/download.py
+@@ -61,18 +61,24 @@ def get_url_from_gdrive_confirmation(contents):
+     return url
+ 
+ 
++def _sanitize_filename(filename):
++    filename = filename.replace("\x00", "")
++    filename = filename.replace("/", "_").replace("\\", "_").strip()
++    if filename in ("", ".", ".."):
++        return "_"
++    return filename
++
++
+ def _get_filename_from_response(response):
+     content_disposition = 
urllib.parse.unquote(response.headers["Content-Disposition"])
+ 
+     m = re.search(r"filename\*=UTF-8''(.*)", content_disposition)
+     if m:
+-        filename = m.groups()[0]
+-        return filename.replace(osp.sep, "_")
++        return _sanitize_filename(m.groups()[0])
+ 
+     m = re.search('attachment; filename="(.*?)"', content_disposition)
+     if m:
+-        filename = m.groups()[0]
+-        return filename
++        return _sanitize_filename(m.groups()[0])
+ 
+     return None
+ 
+@@ -283,7 +289,7 @@ def download(
+         filename_from_url = _get_filename_from_response(response=res)
+         last_modified_time = _get_modified_time_from_response(response=res)
+     if filename_from_url is None:
+-        filename_from_url = osp.basename(url)
++        filename_from_url = _sanitize_filename(osp.basename(url))
+ 
+     if output is None:
+         output = filename_from_url
+diff --git a/gdown/download_folder.py b/gdown/download_folder.py
+index 8c59c96..f38dadb 100644
+--- a/gdown/download_folder.py
++++ b/gdown/download_folder.py
+@@ -12,6 +12,7 @@ from typing import Union
+ import bs4
+ 
+ from .download import _get_session
++from .download import _sanitize_filename
+ from .download import download
+ from .exceptions import FolderContentsMaximumLimitError
+ from .parse_url import is_google_drive_url
+@@ -182,7 +183,7 @@ def _get_directory_structure(gdrive_file, previous_path):
+ 
+     directory_structure = []
+     for file in gdrive_file.children:
+-        file.name = file.name.replace(osp.sep, "_")
++        file.name = _sanitize_filename(file.name)
+         if file.is_folder():
+             directory_structure.append((None, osp.join(previous_path, 
file.name)))
+             for i in _get_directory_structure(file, osp.join(previous_path, 
file.name)):
+@@ -283,6 +284,8 @@ def download_folder(
+         print("Failed to retrieve folder contents", file=sys.stderr)
+         return None
+ 
++    gdrive_file.name = _sanitize_filename(gdrive_file.name)
++
+     if not quiet:
+         print("Retrieving folder contents completed", file=sys.stderr)
+         print("Building directory structure", file=sys.stderr)
+diff --git a/gdown/extractall.py b/gdown/extractall.py
+index 6846026..dd22427 100644
+--- a/gdown/extractall.py
++++ b/gdown/extractall.py
+@@ -1,8 +1,16 @@
++import os
+ import os.path as osp
++import sys
+ import tarfile
+ import zipfile
+ 
+ 
++def _is_within_directory(directory, target):
++    abs_directory = osp.realpath(directory)
++    abs_target = osp.realpath(target)
++    return abs_target.startswith(abs_directory + os.sep) or abs_target == 
abs_directory
++
++
+ def extractall(path, to=None):
+     """Extract archive file.
+ 
+@@ -18,31 +26,61 @@ def extractall(path, to=None):
+         to = osp.dirname(path)
+ 
+     if path.endswith(".zip"):
+-        opener, mode = zipfile.ZipFile, "r"
+-    elif path.endswith(".tar"):
+-        opener, mode = tarfile.open, "r"
++        return _extractall_zip(path, to)
++
++    if path.endswith(".tar"):
++        tar_mode = "r"
+     elif path.endswith(".tar.gz") or path.endswith(".tgz"):
+-        opener, mode = tarfile.open, "r:gz"
++        tar_mode = "r:gz"
+     elif path.endswith(".tar.bz2") or path.endswith(".tbz"):
+-        opener, mode = tarfile.open, "r:bz2"
++        tar_mode = "r:bz2"
+     else:
+         raise ValueError(
+             "Could not extract '%s' as no appropriate " "extractor is found" 
% path
+         )
+ 
+-    def namelist(f):
+-        if isinstance(f, zipfile.ZipFile):
+-            return f.namelist()
+-        return [m.path for m in f.members]
++    return _extractall_tar(path, to, tar_mode)
+ 
+-    def filelist(f):
+-        files = []
+-        for fname in namelist(f):
+-            fname = osp.join(to, fname)
+-            files.append(fname)
+-        return files
+ 
+-    with opener(path, mode) as f:
++def _extractall_zip(path, to):
++    with zipfile.ZipFile(path, "r") as f:
++        names = f.namelist()
++        for member in names:
++            member_path = osp.join(to, member)
++            if not _is_within_directory(to, member_path):
++                raise ValueError(
++                    "Archive member '%s' would extract outside "
++                    "target directory: %s" % (member, to)
++                )
+         f.extractall(path=to)
++    return [osp.join(to, name) for name in names]
++
++
++def _extractall_tar(path, to, tar_mode):
++    with tarfile.open(path, tar_mode) as f:
++        members = f.getmembers()
++        if sys.version_info >= (3, 12):
++            f.extractall(path=to, filter="data")
++        else:
++            for member in members:
++                if member.issym() or member.islnk():
++                    raise ValueError(
++                        "Archive member '%s' is a link, "
++                        "which is not allowed for security reasons"
++                        % member.name
++                    )
++                if member.ischr() or member.isblk() or member.isfifo():
++                    raise ValueError(
++                        "Archive member '%s' is a special file, "
++                        "which is not allowed for security reasons"
++                        % member.name
++                    )
++                member_path = osp.join(to, member.name)
++                if not _is_within_directory(to, member_path):
++                    raise ValueError(
++                        "Archive member '%s' would extract outside "
++                        "target directory: %s" % (member.name, to)
++                    )
++            f.extractall(path=to)
+ 
+-    return filelist(f)
++    return [osp.join(to, m.path) for m in members]
+-- 
+2.47.3
+
diff -Nru gdown-5.2.0+dfsg/debian/patches/series 
gdown-5.2.0+dfsg/debian/patches/series
--- gdown-5.2.0+dfsg/debian/patches/series      1970-01-01 02:00:00.000000000 
+0200
+++ gdown-5.2.0+dfsg/debian/patches/series      2026-06-09 23:11:29.000000000 
+0300
@@ -0,0 +1 @@
+0001-fix-prevent-path-traversal-in-archive-extraction-and.patch

--- End Message ---
--- Begin Message ---
Version: 13.6

This update was released as part of 13.6.

--- End Message ---

Reply via email to