CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-113719

CVE-2026-113719: Arbitrary File Deletion via Path Traversal in virtualenv Seed Package Metadata

Amit Schendel
Amit Schendel
Senior Security Researcher

Oct 24, 2026·6 min read·2 visits

Executive Summary (TL;DR)

virtualenv < 21.14.9 fails to validate path boundaries when parsing seed metadata during environment re-creation, allowing local malicious metadata to trigger arbitrary file deletion.

A path traversal vulnerability in PyPA virtualenv prior to version 21.14.9 allows local attackers to cause arbitrary file and directory deletion when a virtual environment target directory containing untrusted distribution metadata is reused or reseeded.

Vulnerability Overview

virtualenv is the standard library and command-line package used across the Python ecosystem to construct isolated Python environments. During environment instantiation, virtualenv utilizes seed mechanisms—predominantly the embed app-data seeder—to populate target environments with base utilities such as pip, setuptools, and wheel.

Prior to version 21.14.9, when virtualenv is directed to create or reseed an environment at a target destination directory (dest) that already exists, it performs a cleanup routine to remove pre-existing seed distributions before copying new ones. This uninstallation logic inspects existing .dist-info metadata folders inside site-packages to identify which files and package subdirectories must be removed.

When processing package metadata files such as top_level.txt and RECORD, virtualenv failed to verify that paths derived from these files remained within the boundary of the virtual environment destination. If a target directory contains metadata crafted with directory traversal elements (..), absolute paths, or symbolic links, virtualenv processes those paths and executes recursive file deletion routines outside the environment boundary under the execution rights of the invoking user.

Root Cause Analysis

The root cause of CVE-2026-113719 lies in the static method PipInstall._uninstall_dist() within virtualenv.seed.embed.via_app_data.pip_install.base. When uninstallation of a pre-existing package distribution is required, _uninstall_dist resolves the parent directory of the distribution metadata (dist.parent, which corresponds to site-packages) and reads metadata files to build a list of target paths to delete.

The routine reads top_level.txt line-by-line and appends each line directly to dist.parent. It then parses the RECORD CSV file, concatenates each relative entry with dist_base, and aggregates all derived target Path objects into a set. At no point during path collection or iteration did the code verify that the target paths were contained within the environment root (self._creator.dest).

Additionally, edge cases in metadata formatting aggravated the issue. A blank line within top_level.txt resolved to dist.parent (site-packages), causing virtualenv to attempt deletion of the entire package directory. Relative path entries beginning with sequence constructs like ../../ allowed deletion of files outside the virtual environment tree, including system files or user data files to which the executing user had write permissions.

Code Analysis & Patch Assessment

In patched version 21.14.9, _uninstall_previous_version() passes self._creator.dest into _uninstall_dist(dist, env_dir) to enforce target directory containment. The fixed code canonicalizes the environment target directory as root = env_dir.resolve() and explicitly validates every candidate path before executing unlinking operations.

# Vulnerable Implementation (virtualenv < 21.14.9)
@staticmethod
def _uninstall_dist(dist: Path) -> None:
    dist_base = dist.parent
    LOGGER.debug("uninstall existing distribution %s from %s", dist.stem, dist_base)
    top_txt = dist / "top_level.txt"
    paths = (
        {dist.parent / i.strip() for i in top_txt.read_text(encoding="utf-8").splitlines()}
        if top_txt.exists()
        else set()
    )
    paths.add(dist)
    base_dirs, record = paths.copy(), dist / "RECORD"
    for name in (
        (i.split(",")[0] for i in record.read_text(encoding="utf-8").splitlines()) if record.exists() else ()
    ):
        path = dist_base / name
        if not any(p in base_dirs for p in path.parents):
            paths.add(path)
    for path in sorted(paths):
        if path.exists():
            if path.is_dir() and not path.is_symlink():
                safe_delete(path)
            else:
                path.unlink()

The fix introduces concrete boundary checks using Path.resolve() and checks parent relationships against root:

# Fixed Implementation (virtualenv 21.14.9)
@staticmethod
def _uninstall_dist(dist: Path, env_dir: Path) -> None:
    dist_base: Final = dist.parent
    # ... path collection logic ...
    root: Final = env_dir.resolve()
    metadata_parents: Final = set(dist.resolve().parents)
    for entry in sorted(paths):
        if entry.exists() or entry.is_symlink():
            linked: Final = entry.is_symlink()
            # Do not follow symlinks for boundary calculation
            resolved: Final = entry.parent.resolve() / entry.name if linked else entry.resolve()
            if root not in resolved.parents or resolved in metadata_parents:
                LOGGER.warning(
                    "skip removal of %s listed by %s: not inside environment %s or a parent of the metadata",
                    entry,
                    dist.name,
                    root,
                )
            elif entry.is_dir() and not linked:
                safe_delete(entry)
            else:
                entry.unlink()

The fix completely mitigates the flaw by checking that root in resolved.parents and verifying that candidate entries are not parents of the .dist-info metadata folder itself. Symlink handling ensures that unlinking a symlink removes only the link without traversing into arbitrary target destinations.

Attack Methodology & Exploitation Mechanics

Exploitation relies on local pre-conditions where a target directory is shared, reused, or pre-populated by a lower-privileged local attacker. Consider a multi-user environment or shared build runner where directory /tmp/shared_env is reused across pipeline jobs or user sessions without issuing the --clear flag.

An attacker with write permissions to /tmp/shared_env creates a fake distribution metadata path such as /tmp/shared_env/lib/python3.10/site-packages/pip-23.0.dist-info/RECORD. Inside this RECORD file, the attacker embeds entries containing traversal relative paths, such as ../../../../home/victim/.ssh/authorized_keys,sha256=...,100.

When a victim executes virtualenv /tmp/shared_env, the via_app_data seeder invokes _uninstall_previous_version(). The seeder parses the injected RECORD file, constructs the path /tmp/shared_env/lib/python3.10/site-packages/../../../../home/victim/.ssh/authorized_keys, and invokes entry.unlink(). The operation succeeds using the victim's privileges, resulting in target file deletion.

Impact Assessment

The severity of CVE-2026-113719 is rated Medium with a CVSS v3.1 base score of 4.5 (CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L). The primary security impact is local arbitrary file and directory deletion. While the vulnerability does not directly grant remote code execution or privilege escalation, arbitrary file deletion can lead to denial of service or indirect system compromise if critical configuration files or authentication credentials are destroyed.

Exploitation requires specific conditions: local system access, write access to a target directory prior to virtualenv execution, and reuse of that environment path without the --clear parameter. Execution in clean, non-existent directories or invocations with --clear are unaffected because pre-existing metadata cleanup is bypassed.

Because deletion occurs with the identity and privileges of the user running virtualenv, running virtualenv as a privileged user (such as root or inside CI/CD containers with elevated rights) increases the blast radius of potential file destruction.

Remediation & Mitigation Strategies

To resolve CVE-2026-113719, update virtualenv to version 21.14.9 or higher. The fixed version enforces canonical boundary checks on all seed package metadata entries.

In environments where upgrading virtualenv immediately is not feasible, implement operational controls:

  1. Always pass the --clear flag when re-creating or reusing existing virtual environment paths (virtualenv --clear /path/to/env).
  2. Avoid running virtualenv against world-writable or shared directories such as /tmp or shared group workspaces where untrusted local accounts can manipulate directory structures.
  3. Ensure target virtual environment parent directories maintain restrictive permissions (0700) owned exclusively by the target executing user.

Official Patches

PyPAPull Request #3428: Limit seed-package cleanup to entries inside the environment
PyPAvirtualenv Release 21.14.9

Fix Analysis (1)

Technical Appendix

CVSS Score
4.5/ 10
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L
EPSS Probability
0.12%
Top 100% most exploited

Affected Systems

virtualenv versions < 21.14.9 on Python environments (Linux, macOS, Windows)

Affected Versions Detail

Product
Affected Versions
Fixed Version
virtualenv
PyPA
< 21.14.921.14.9
AttributeDetail
Vulnerability IDCVE-2026-113719
CWE IDCWE-22
CVSS v3.1 Score4.5 (Medium)
CVSS VectorCVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L
EPSS Score0.00117 (1.57th percentile)
ImpactArbitrary File Deletion
Exploit StatusProof of Concept / Local Pre-condition
CISA KEV StatusNot Listed

MITRE ATT&CK Mapping

T1083File and Directory Discovery
Discovery
T1005Data from Local System
Collection
CWE-22
Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

The software uses external input to construct a pathname that should be restricted to a directory within a restricted parent directory, but does not properly sanitize path elements that resolve outside the target path.

Known Exploits & Detection

GitHub Security AdvisoryAdvisory details path traversal vulnerability during seed cleanup.

Vulnerability Timeline

Fix commit e3cea6fa10a50fa29cf1fdddd772458fba076d5f committed to pypa/virtualenv repository.
2026-10-17
GitHub Advisory GHSA-hhxq-47xx-j5xx and CVE-2026-113719 published.
2026-10-20
virtualenv version 21.14.9 published to PyPI containing patch.
2026-10-20
NVD CVE record populated.
2026-10-20

References & Sources

  • [1]GitHub Security Advisory GHSA-hhxq-47xx-j5xx
  • [2]Fix Commit in pypa/virtualenv
  • [3]Pull Request #3428: Limit seed-package cleanup to entries inside the environment
  • [4]virtualenv Release Tag 21.14.9
  • [5]NVD CVE-2026-113719 Detail
  • [6]CVE.org Record for CVE-2026-113719

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•about 1 hour ago•CVE-2026-113715
2.0

CVE-2026-113715: Configuration Key Injection via Unicode Line Boundary in virtualenv

In virtualenv versions prior to 21.14.9, creating isolated Python virtual environments using the standard library creator (`--creator venv`) allows configuration key injection into `pyvenv.cfg` if an attacker can control or influence the destination directory path containing Unicode line break characters.

Amit Schendel
Amit Schendel
4 views•5 min read
•about 2 hours ago•CVE-2026-113723
7.1

CVE-2026-113723: OS Command Injection via Unsafe Prompt Expansion in PyPA virtualenv

A command injection vulnerability in PyPA virtualenv prior to version 21.14.9 allows local attackers to execute arbitrary commands when a user sources a generated shell activation script containing a crafted prompt value.

Alon Barad
Alon Barad
4 views•5 min read
•about 3 hours ago•CVE-2026-113720
4.4

CVE-2026-113720: Local Permission Inheritance Vulnerability in virtualenv

virtualenv prior to version 21.14.9 contains a local permission inheritance vulnerability in its app-data copy seeder. Due to the reliance on Python's standard shutil.copy() function, permissions from cached packages are replicated directly onto newly created virtual environment files, ignoring the active process umask and allowing unprivileged local attackers to inject code.

Amit Schendel
Amit Schendel
7 views•5 min read
•about 4 hours ago•CVE-2026-113718
7.1

CVE-2026-113718: Code Injection in pypa/virtualenv XonshActivator via Multibyte Encoding Misalignment

A code injection vulnerability in pypa/virtualenv prior to version 21.14.9 allows arbitrary command execution in xonsh shells using legacy multibyte encodings when sourcing activate.xsh.

Amit Schendel
Amit Schendel
6 views•5 min read
•about 5 hours ago•CVE-2026-113714
2.3

CVE-2026-113714: Cache Poisoning and Unvalidated Wheel Download in virtualenv

Prior to version 21.14.9, virtualenv downloads wheel files directly into its reusable persistent cache directory prior to verifying PyPI digests or compatibility. Combined with unsanitized stdout parsing from pip, a controlled package index can cause virtualenv to calculate digests against pre-existing local files while leaving unverified archives in the shared persistent cache.

Amit Schendel
Amit Schendel
6 views•5 min read
•about 6 hours ago•CVE-2026-113717
2.3

CVE-2026-113717: Package Digest Verification Bypass in virtualenv Pip Seeder

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.

Alon Barad
Alon Barad
6 views•5 min read