Oct 24, 2026·5 min read·1 visit
virtualenv prior to 21.14.9 fails to enforce internal SHA-256 digest verification when seeding environments with '--seeder pip --download', allowing pip to fetch and install wheels without PyPI hash validation.
A verification bypass vulnerability in virtualenv allows unverified Python wheel installation when using pip seeding with the download flag. Affected versions fail to perform SHA-256 digest validation against PyPI metadata before invoking pip.
virtualenv is a standard Python library used to create isolated Python virtual environments. When creating an environment, virtualenv installs essential seed packages including pip, setuptools, and wheel. The population of these packages is governed by internal seeder implementations, such as the pip_invoke module.
In versions of virtualenv prior to 21.14.9, invoking virtualenv with the flags '--seeder pip' and '--download' causes virtualenv to bypass its internal digest verification workflow. Under normal operating conditions, virtualenv fetches wheel metadata from PyPI and compares the downloaded wheel's SHA-256 digest against expected PyPI checksums before proceeding with installation.
The attack surface is present in software build processes, automated CI/CD pipelines, and developer environments that invoke virtualenv with pip seeding and wheel download options enabled. The issue is tracked under CWE-494: Download of Code Without Integrity Check.
The root cause is located in 'src/virtualenv/seed/embed/pip_invoke.py' within the 'PipInvoke.get_pip_install_cmd()' method. When generating the required installation arguments for seed packages, 'get_pip_install_cmd()' passed 'download=False' to the internal helper 'get_wheel()', despite 'self.download' being set to True.
Because 'download=False' was supplied, 'get_wheel()' refrained from invoking 'download_wheel()' in 'src/virtualenv/seed/wheels/acquire.py'. The 'download_wheel()' function contains 'verify_wheel_digest()', which is the sole function responsible for validating the wheel digest against PyPI records. Skipping 'download_wheel()' effectively removed cryptographic integrity verification.
Simultaneously, 'get_pip_install_cmd()' conditionally appended the '--no-index' parameter to the pip command string only when 'not self.download' evaluated to True. When 'self.download' was active, '--no-index' was omitted. As a result, pip was executed with permission to query configured external package indices, but without virtualenv having verified the wheel's SHA-256 hash.
An inspection of commit 7babec66e043e1cc8bc66869a063653898719d96 in Pull Request #3430 demonstrates the architectural correction. First, 'PipInvoke.get_pip_install_cmd()' was modified to pass 'download=self.download' directly to 'get_wheel()', ensuring that 'download_wheel()' and 'verify_wheel_digest()' are executed when downloads are requested.
Second, 'download_wheel()' in 'src/virtualenv/seed/wheels/acquire.py' was updated to download wheels into a dedicated 'TemporaryDirectory' staging area prefixed with 'download-'. SHA-256 digest verification is performed on the file in this temporary location before it is transferred to the permanent destination cache.
Third, the pip command generated by PipInvoke now explicitly includes '--no-index' across all execution paths. Network resolution by pip is prevented, and package resolution is limited to pre-verified wheels in a local directory passed via '--find-links'.
# Vulnerable Implementation in src/virtualenv/seed/embed/pip_invoke.py
if not self.download:
cmd.append("--no-index")
wheel = get_wheel(
distribution,
version,
self.for_py_version,
search_dirs,
download=False, # Hardcoded False skipped download_wheel() and verify_wheel_digest()
app_data=self.app_data,
)
# Fixed Implementation in Commit 7babec66e043e1cc8bc66869a063653898719d96
cmd: Final = [
...
"--no-index", # Disables external pip index queries
]
with TemporaryDirectory(prefix="virtualenv-seed-") as folder:
cmd.extend(["--find-links", folder])
for dist, version in self.distribution_to_versions().items():
wheel: Final = get_wheel(
dist,
version,
self.for_py_version,
search_dirs,
download=self.download, # Passes actual download boolean
app_data=self.app_data,
)
copyfile(wheel.path, Path(folder) / wheel.name)
cmd.append(Version.as_pip_req(dist, wheel.version))
yield cmdExploitation requires specific environmental conditions. An attacker cannot trigger the flaw without execution of virtualenv using '--seeder pip' and '--download'. Additionally, the environment must resolve package queries to a malicious index, an untrusted package mirror, or be subject to a network position capable of intercepting traffic.
When virtualenv runs under these conditions on an unpatched version, pip executes external lookups. Because virtualenv omitted '--no-index' and failed to verify the hash prior to pip execution, pip retrieves and installs candidate wheels matching the requested name and version without checking PyPI digest metadata.
The diagram below outlines the sequence leading to unverified wheel execution:
Successful exploitation leads to the installation of arbitrary Python wheel packages into newly created virtual environments. If a malicious wheel is supplied, any installation hooks or code included in the package execute with the privileges of the user running virtualenv.
In continuous integration and build environments, this vulnerability permits supply chain compromise, potential exfiltration of CI secrets, or modification of built application artifacts.
The CVSS v4.0 base score is evaluated at 2.3 (Low) with vector 'CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:P/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N'. Severity is limited due to the required execution flags, network prerequisites, and user interaction conditions.
The primary remediation is updating virtualenv to version 21.14.9 or higher. The fixed version forces all wheel downloads through isolation staging directories and enforces SHA-256 digest validation before invoking pip.
If upgrading immediately is not possible, avoid using the combination of '--seeder pip' and '--download'. Standard seeder configurations or pre-cached wheels are not affected by this bypass.
When using non-PyPI mirrors, ensure index URLs are explicitly defined using official environment variables ('PIP_INDEX_URL' and 'PIP_EXTRA_INDEX_URL') and verify that TLS enforcement is strictly active.
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:P/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
virtualenv PyPA | < 21.14.9 | 21.14.9 |
| Attribute | Detail |
|---|---|
| Vulnerability ID | CVE-2026-113717 / GHSA-cx28-75q4-x26q |
| CWE ID | CWE-494 (Download of Code Without Integrity Check) |
| CVSS Score | 2.3 (CVSS v4.0 Low) |
| EPSS Score | 0.00297 (21.21st percentile) |
| Attack Vector | Network (Requires specific CLI flags and index configuration) |
| Exploit Status | No public weaponized exploit |
| Patched Version | virtualenv 21.14.9 |
The application downloads executable code or packages from a remote source without verifying the SHA-256 hash digest or cryptographic signature against expected metadata.
Insecure temporary file usage (CWE-377) in virtualenv app-data seeder creates dangling symlinks pointing to deleted temporary directories, allowing local attackers on shared systems to execute arbitrary code.
A Time-of-Check Time-of-Use (TOCTOU) race condition in the Python `filelock` library (versions 4.1.0 to 4.2.0) allows local unprivileged attackers to cause arbitrary directory deletion by swapping file system targets during lock cleanup or dead owner sweeps.
A local race condition and mutual exclusion bypass vulnerability exists in filelock versions 4.1.0 through 4.2.0. On Linux filesystems with rapid inode reuse, a local attacker can manipulate modification timestamps and lock files to cause waiting processes to falsely evict active soft lock markers.
vLLM prior to version 0.31.0 contains an uncontrolled resource consumption vulnerability (CWE-400) in its HTTP API request handling logic. Unauthenticated remote attackers can transmit per-request keyword arguments (`mm_processor_kwargs` and `media_io_kwargs`) that are passed directly into internal multimodal preprocessing pipelines, causing CPU/GPU memory exhaustion and crashing the engine process.
An insufficient verification of data authenticity vulnerability in SiYuan prior to version 3.7.4 allows an attacker to overwrite arbitrary installed marketplace packages (plugins, widgets, themes, and templates). By providing a mismatched package name and package archive repository URL during a Bazaar installation request, arbitrary file write into trusted plugin directories can be achieved, enabling persistence and execution across application restarts.
In obsidian-tc versions 1.0.1 through 1.31.3, memory lifecycle tools fail to validate note ownership prior to rematerializing Markdown files and execute non-atomic SQLite database mutations. An authorized caller can overwrite arbitrary note bodies on disk and cause persistent desynchronization between the database memory graph and physical vault files.