Oct 23, 2026·5 min read·3 visits
virtualenv prior to 21.14.9 follows dangling symlinks during environment setup, allowing local attackers to write files outside the target directory.
In Python virtualenv prior to version 21.14.9, activation script generators and configuration writers use Path.exists() before writing files. Because Path.exists() resolves symbolic links, it returns False for dangling symlinks. Consequently, virtualenv skips unlinking pre-planted symbolic links or hard links, writing data directly to target locations outside the intended virtual environment directory.
The Python Package Index (virtualenv) tool creates isolated Python execution environments. When constructing a target environment, virtualenv writes activation scripts (activate, activate.ps1, activate.csh), seed launchers, import hooks (_virtualenv.pth), and configuration files (pyvenv.cfg). During file generation, affected versions evaluate destination file paths using pathlib.Path.exists() before overwriting existing files.
Because pathlib.Path.exists() traverses symbolic links, it resolves to False if a target file pointed to by a symbolic link does not exist. This creates a Link Following vulnerability (CWE-59). When a dangling symbolic link exists in the target directory, virtualenv treats the destination as non-existent and skips the step intended to remove pre-existing files.
The attack surface requires local directory access where an environment creation process occurs. In shared build servers, multi-tenant developer systems, or CI/CD pipelines operating in shared directory spaces (e.g., /tmp or persistent scratch spaces), standard low-privileged users can pre-stage symbolic links or hard links in anticipation of automated virtualenv creation.
The fundamental flaw resides in the difference between link existence and link target existence in Python standard library path handling. In src/virtualenv/activation/via_template.py and related configuration components, file creation logic was implemented using standard existence checks prior to writing content.
When Path.exists() is called on a symbolic link, Python's pathlib module attempts to resolve the underlying destination path via stat(). If the destination target does not exist on the file system, the operation fails and Path.exists() returns False. The logic in virtualenv interpreted this boolean response as an indication that no object occupied the destination file path.
As a consequence of this false negative, virtualenv bypassed its internal dest.unlink() procedure. Subsequent calls to dest.write_bytes() or file stream open operations opened the existing file path. The operating system resolved the symbolic link and opened the target destination file outside the virtual environment root, creating or overwriting the file with the invoking user's file permissions.
In affected versions of virtualenv, template generation methods used vulnerable control flow when writing activation files:
# Vulnerable logic in virtualenv/activation/via_template.py
dest = to_folder / self.as_name(template)
if dest.exists():
dest.unlink()
dest.write_bytes(text.encode(encoding))If dest is a dangling symlink, dest.exists() evaluates to False, skipping dest.unlink(). The execution proceeds directly to dest.write_bytes(), which follows the link to the external path.
The upstream fix in commit d25f28699eefb1fc34a251e5adf2b5de844c36cd replaces direct write_bytes() calls with a dedicated helper virtualenv.util.path.write_file and introduces strict path boundary validation:
# Patched helper in src/virtualenv/util/path/_sync.py
def write_file(path: Path, data: bytes) -> None:
# Check for symbolic links explicitly without resolving targets
if path.is_symlink() or path.exists():
path.unlink()
# Exclusive creation flag 'x' prevents opening existing target files
with path.open("xb") as stream:
stream.write(data)Additionally, the patch introduces ensure_within(root, path) to verify intermediate directories for Windows NTFS junction points and symbolic link components. In standard library venv creation routines, virtualenv explicitly checks and removes symbolic links or multi-link hard files (st_nlink > 1) before invocation.
To exploit this vulnerability, an attacker requires local write permissions to the directory where another user or system process intends to create a virtual environment. The attacker pre-populates target file paths with symbolic links pointing to restricted destinations owned by or writeable by the victim user.
For example, an attacker pre-creates the path /tmp/shared_env/bin/activate as a dangling symbolic link pointing to /home/victim/.bashrc. When the victim user runs virtualenv /tmp/shared_env, virtualenv evaluates Path.exists() on /tmp/shared_env/bin/activate.
Because /home/victim/.bashrc may not exist or fails target checks, or in cases where configuration targets like pyvenv.cfg point to custom target files, virtualenv bypasses deletion and opens the path. The shell activation script data is written into the victim file location. The impact is limited to file creation or modification with the content generated by virtualenv.
The CVSS v3.1 base score for CVE-2026-113721 is 4.5 (Medium), with vector CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L. Attack complexity is rated high because successful exploitation relies on race conditions or specific local directory staging where the target path is predictable and writeable by the attacker.
Primary impact involves arbitrary file creation and content modification under the privilege context of the user executing virtualenv. An attacker cannot write arbitrary payloads, as the injected content is constrained to valid configuration values (pyvenv.cfg) or activation script text generated by virtualenv.
However, corrupting environment configuration files or overwriting key user configuration files can result in localized denial of service or execution redirection if an overwritten file is subsequently executed or sourced by the target user.
Maintainers resolved this vulnerability in virtualenv version 21.14.9. System administrators and developers should update all environment installations across build tools, CI/CD runners, and active virtual environments.
To update virtualenv using pip, execute:
pip install --upgrade "virtualenv>=21.14.9"Where immediate updates cannot be deployed, restrict directory permissions on parent directories used for environment creation. Ensure temporary build spaces use private directories (chmod 700) rather than shared, world-writable directories.
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
virtualenv PyPA | < 21.14.9 | 21.14.9 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-59 |
| Attack Vector | Local |
| CVSS Score | 4.5 (Medium) |
| EPSS Score | 0.00117 (1.57th percentile) |
| Impact | Arbitrary File Creation / Overwrite |
| Exploit Status | No Active Exploitation |
| CISA KEV Status | Not Listed |
The software attempts to access a file based on a filename, but it does not properly resolve symbolic links or hard links, allowing an attacker to traverse to unintended locations.
A heap-based buffer overflow vulnerability in Pillow PyPI binary wheels (versions 11.1.0 to 12.x) allows remote code execution or application crash via crafted JPEG 2000 files processed by the bundled libopenjp2 library.
CVE-2026-109618 (dubbed PHP2Shell) is a critical vulnerability in the PHP engine core resulting from improper out-of-memory exception handling via longjmp(). When request allocation fails mid-processing, non-local control flow bypasses cleanup procedures, leaving memory structures uninitialized or in a use-after-free state during request shutdown.
CVE-2026-109619 (PHP2Shell) is a critical Use-After-Free vulnerability in the core PHP engine caused by improper global state cleanup during out-of-memory exception handling across persistent worker requests.
A remote code execution vulnerability exists in Microsoft .NET due to untrusted data deserialization within the PresentationCore component. The flaw stems from implicit auto-conversion flags during clipboard data retrieval operations.
An out-of-bounds write vulnerability in Microsoft .NET Framework, .NET runtime, and Visual Studio allows unauthenticated remote attackers to execute arbitrary code via crafted pixel-shader bytecode or auto-converted clipboard payloads.
ASP.NET Core contains a thread deadlock vulnerability (CWE-833) that allows an unauthenticated remote attacker to cause a denial of service over the network by exhausting server thread pool resources.