CVE-2026-113721
virtualenv: Generated files can follow existing destination links
virtualenv is a tool for creating isolated virtual python environments. Prior to 21.14.9, ViaTemplateActivator._generate() removes an existing output only when Path.exists() is true, allowing a dangling symlink to survive and redirect generated activation content, while PyEnvCfg.write() opens pyvenv.cfg without first replacing an existing symbolic or hard link. Another local account that prepares final entries inside the destination can therefore cause environment creation to create or overwrite files outside the requested tree with constrained virtualenv-generated content and the invoking user's permissions. The destination parent must remain controlled by the environment owner during creation; concurrent parent-directory replacement is a separate concern. This issue is fixed in version 21.14.9.
Attack profile
How easy it is to exploit
- Attack vector
- Local
- Complexity
- High
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- Low
- Integrity
- Low
- Availability
- Low
Exploit likelihood
What each source reports
CVE List Score used
4.5
virtualenv: Generated files can follow existing destination links
virtualenv is a tool for creating isolated virtual python environments. Prior to 21.14.9, ViaTemplateActivator._generate() removes an existing output only when Path.exists() is true, allowing a dangling symlink to survive and redirect generated activation content, while PyEnvCfg.write() opens pyvenv.cfg without first replacing an existing symbolic or hard link. Another local account that prepares final entries inside the destination can therefore cause environment creation to create or overwrite files outside the requested tree with constrained virtualenv-generated content and the invoking user's permissions. The destination parent must remain controlled by the environment owner during creation; concurrent parent-directory replacement is a separate concern. This issue is fixed in version 21.14.9.
- Record
CVE-2026-113721- CVSS 3.1
- 4.5 Medium
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L - Weakness
- CWE-59
- Updated
- 2026-11-02
EUVDEUVD-2026-105303
4.5
virtualenv is a tool for creating isolated virtual python environments. Prior to 21.14.9, ViaTemplateActivator._generate() removes an existing output only when Path.exists() is true, allowing a dangling symlink to survive and redirect generated activation content, while PyEnvCfg.write() opens pyvenv.cfg without first replacing an existing symbolic or hard link. Another local account that prepares final entries inside the destination can therefore cause environment creation to create or overwrite files outside the requested tree with constrained virtualenv-generated content and the invoking user's permissions. The destination parent must remain controlled by the environment owner during creation; concurrent parent-directory replacement is a separate concern. This issue is fixed in version 21.14.9.
- Record
EUVD-2026-105303- CVSS 3.1
- 4.5 Medium
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-558c-9hv2-4prj
4.5
virtualenv: Generated files can follow existing destination links
Summary
Generated activation files and pyvenv.cfg can follow existing links and write outside the requested environment. The template writer tests exists() before unlinking, which misses dangling symlinks; the configuration writer opens the existing path directly.
Anthropic's OSS Scanner reported this as ANT-2026-XBEOCBS1 in virtualenv-20261015-005, received on 15 October 2026.
Affected area
Final file entries written during environment creation and seeding.
Attacker-controlled input
Another account must be able to prepare file links in the destination before creation. The invoking user must have write permission at the linked target. The destination parent must remain controlled during creation; concurrent parent-directory replacement is a separate concern.
Affected code
The relevant 21.14.8 source is in template writer, configuration writer.
Mechanism
ViaTemplateActivator._generate() removes an existing output only when Path.exists() is true. A dangling symlink
therefore survives, and write_bytes() creates its target outside the environment. Other generated files have related
final-entry behavior. PyEnvCfg.write() opens pyvenv.cfg without first replacing an existing link; a symlink or hard
link there can overwrite an existing target. These paths require a prepared destination tree and the invoking user's
permission to write the linked target.
Reproduction and evidence
The scanner's project tree contains venv/bin/activate -> ../../../.bash_aliases and an executable venv/bin/basename.
Before creation, a new interactive bash does not create the marker. A plain virtualenv venv writes the activation
script through the dangling link, creating ~/.bash_aliases as a regular file. Debian's stock .bashrc sources that
file; the next interactive bash adds venv/bin to PATH and the planted basename writes the marker. The reproducer
checks that the link remains, ~/.bash_aliases appears, and the second shell executes the planted file. A separate
manual check links pyvenv.cfg to ~/.bashrc and observes the latter replaced with generated configuration text.
The link lets creation write outside the requested environment. The attacker chooses the target path but the generated bytes come from virtualenv. The executable in the example was already in the prepared tree; this finding is not arbitrary code injection into an otherwise trusted destination. The scanner traces the template behavior to #1454, first released in 20.0.0b1. PR #3431 replaces pre-existing output links and uses exclusive creation for generated text. It does not make a destination safe if another account can replace parent directories during creation.
Attack sequence
sequenceDiagram
participant A as Prepared project tree
participant V as virtualenv writer
participant H as User home
participant B as Next interactive bash
A->>V: venv/bin/activate links to missing ~/.bash_aliases
V->>V: Path.exists() follows link and returns false
V->>H: write activation bytes through link
H-->>B: ~/.bash_aliases now exists
B->>H: .bashrc sources ~/.bash_aliases
B->>A: PATH reaches planted venv/bin/basename
A-->>B: marker created
Note over V,H: pyvenv.cfg can also overwrite an existing linked target
Reproducer files
The following files are from the scanner report for ANT-2026-XBEOCBS1. They were tested against commit a857a7823c73
in the scanner image. The repro entrypoint expects companion files under /tmp/repro.d; cases that change user IDs
need a disposable root-capable container. The last output line is BUG when the reported behavior occurs. These files
are…
- Record
GHSA-558c-9hv2-4prj- CVSS 3.1
- 4.5 Medium
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L - Weakness
- CWE-59
- Updated
- 2026-11-02
NVD
4.5
virtualenv is a tool for creating isolated virtual python environments. Prior to 21.14.9, ViaTemplateActivator._generate() removes an existing output only when Path.exists() is true, allowing a dangling symlink to survive and redirect generated activation content, while PyEnvCfg.write() opens pyvenv.cfg without first replacing an existing symbolic or hard link. Another local account that prepares final entries inside the destination can therefore cause environment creation to create or overwrite files outside the requested tree with constrained virtualenv-generated content and the invoking user's permissions. The destination parent must remain controlled by the environment owner during creation; concurrent parent-directory replacement is a separate concern. This issue is fixed in version 21.14.9.
- Record
CVE-2026-113721- CVSS 3.1
- 4.5 Medium
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L - Weakness
- CWE-59
- Updated
- 2026-11-02
OSV
4.5
virtualenv: Generated files can follow existing destination links
virtualenv is a tool for creating isolated virtual python environments. Prior to 21.14.9, ViaTemplateActivator._generate() removes an existing output only when Path.exists() is true, allowing a dangling symlink to survive and redirect generated activation content, while PyEnvCfg.write() opens pyvenv.cfg without first replacing an existing symbolic or hard link. Another local account that prepares final entries inside the destination can therefore cause environment creation to create or overwrite files outside the requested tree with constrained virtualenv-generated content and the invoking user's permissions. The destination parent must remain controlled by the environment owner during creation; concurrent parent-directory replacement is a separate concern. This issue is fixed in version 21.14.9.
- Record
CVE-2026-113721- CVSS 3.1
- 4.5 Medium
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L - Weakness
- CWE-59
- Updated
- 2026-11-02
OSVDEBIAN-CVE-2026-113721
4.5
virtualenv is a tool for creating isolated virtual python environments. Prior to 21.14.9, ViaTemplateActivator._generate() removes an existing output only when Path.exists() is true, allowing a dangling symlink to survive and redirect generated activation content, while PyEnvCfg.write() opens pyvenv.cfg without first replacing an existing symbolic or hard link. Another local account that prepares final entries inside the destination can therefore cause environment creation to create or overwrite files outside the requested tree with constrained virtualenv-generated content and the invoking user's permissions. The destination parent must remain controlled by the environment owner during creation; concurrent parent-directory replacement is a separate concern. This issue is fixed in version 21.14.9.
- Record
DEBIAN-CVE-2026-113721- CVSS 3.1
- 4.5 Medium
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L - Updated
- 2026-11-02
Show 5 moreShow less
OSVGHSA-558c-9hv2-4prj
4.5
virtualenv: Generated files can follow existing destination links
Summary
Generated activation files and pyvenv.cfg can follow existing links and write outside the requested environment. The template writer tests exists() before unlinking, which misses dangling symlinks; the configuration writer opens the existing path directly.
Anthropic's OSS Scanner reported this as ANT-2026-XBEOCBS1 in virtualenv-20261015-005, received on 15 October 2026.
Affected area
Final file entries written during environment creation and seeding.
Attacker-controlled input
Another account must be able to prepare file links in the destination before creation. The invoking user must have write permission at the linked target. The destination parent must remain controlled during creation; concurrent parent-directory replacement is a separate concern.
Affected code
The relevant 21.14.8 source is in template writer, configuration writer.
Mechanism
ViaTemplateActivator._generate() removes an existing output only when Path.exists() is true. A dangling symlink
therefore survives, and write_bytes() creates its target outside the environment. Other generated files have related
final-entry behavior. PyEnvCfg.write() opens pyvenv.cfg without first replacing an existing link; a symlink or hard
link there can overwrite an existing target. These paths require a prepared destination tree and the invoking user's
permission to write the linked target.
Reproduction and evidence
The scanner's project tree contains venv/bin/activate -> ../../../.bash_aliases and an executable venv/bin/basename.
Before creation, a new interactive bash does not create the marker. A plain virtualenv venv writes the activation
script through the dangling link, creating ~/.bash_aliases as a regular file. Debian's stock .bashrc sources that
file; the next interactive bash adds venv/bin to PATH and the planted basename writes the marker. The reproducer
checks that the link remains, ~/.bash_aliases appears, and the second shell executes the planted file. A separate
manual check links pyvenv.cfg to ~/.bashrc and observes the latter replaced with generated configuration text.
The link lets creation write outside the requested environment. The attacker chooses the target path but the generated bytes come from virtualenv. The executable in the example was already in the prepared tree; this finding is not arbitrary code injection into an otherwise trusted destination. The scanner traces the template behavior to #1454, first released in 20.0.0b1. PR #3431 replaces pre-existing output links and uses exclusive creation for generated text. It does not make a destination safe if another account can replace parent directories during creation.
Attack sequence
sequenceDiagram
participant A as Prepared project tree
participant V as virtualenv writer
participant H as User home
participant B as Next interactive bash
A->>V: venv/bin/activate links to missing ~/.bash_aliases
V->>V: Path.exists() follows link and returns false
V->>H: write activation bytes through link
H-->>B: ~/.bash_aliases now exists
B->>H: .bashrc sources ~/.bash_aliases
B->>A: PATH reaches planted venv/bin/basename
A-->>B: marker created
Note over V,H: pyvenv.cfg can also overwrite an existing linked target
Reproducer files
The following files are from the scanner report for ANT-2026-XBEOCBS1. They were tested against commit a857a7823c73
in the scanner image. The repro entrypoint expects companion files under /tmp/repro.d; cases that change user IDs
need a disposable root-capable container. The last output line is BUG when the reported behavior occurs. These files
are…
- Record
GHSA-558c-9hv2-4prj- CVSS 3.1
- 4.5 Medium
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L - Weakness
- CWE-59
- Updated
- 2026-11-02
OSVUBUNTU-CVE-2026-113721
4.5
virtualenv is a tool for creating isolated virtual python environments. Prior to 21.14.9, ViaTemplateActivator._generate() removes an existing output only when Path.exists() is true, allowing a dangling symlink to survive and redirect generated activation content, while PyEnvCfg.write() opens pyvenv.cfg without first replacing an existing symbolic or hard link. Another local account that prepares final entries inside the destination can therefore cause environment creation to create or overwrite files outside the requested tree with constrained virtualenv-generated content and the invoking user's permissions. The destination parent must remain controlled by the environment owner during creation; concurrent parent-directory replacement is a separate concern. This issue is fixed in version 21.14.9.
- Record
UBUNTU-CVE-2026-113721- CVSS 3.1
- 4.5 Medium
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L - Updated
- 2026-11-02
OSVAZL-110124 No score
CVE-2026-113721 affecting package python-virtualenv for versions less than 20.36.1-10
virtualenv is a tool for creating isolated virtual python environments. Prior to 21.14.9, ViaTemplateActivator._generate() removes an existing output only when Path.exists() is true, allowing a dangling symlink to survive and redirect generated activation content, while PyEnvCfg.write() opens pyvenv.cfg without first replacing an existing symbolic or hard link. Another local account that prepares final entries inside the destination can therefore cause environment creation to create or overwrite files outside the requested tree with constrained virtualenv-generated content and the invoking user's permissions. The destination parent must remain controlled by the environment owner during creation; concurrent parent-directory replacement is a separate concern. This issue is fixed in version 21.14.9.
- Record
AZL-110124- Updated
- 2026-11-07
OSVBIT-virtualenv-2026-113721 No score
virtualenv: Generated files can follow existing destination links
virtualenv is a tool for creating isolated virtual python environments. Prior to 21.14.9, ViaTemplateActivator._generate() removes an existing output only when Path.exists() is true, allowing a dangling symlink to survive and redirect generated activation content, while PyEnvCfg.write() opens pyvenv.cfg without first replacing an existing symbolic or hard link. Another local account that prepares final entries inside the destination can therefore cause environment creation to create or overwrite files outside the requested tree with constrained virtualenv-generated content and the invoking user's permissions. The destination parent must remain controlled by the environment owner during creation; concurrent parent-directory replacement is a separate concern. This issue is fixed in version 21.14.9.
- Record
BIT-virtualenv-2026-113721- Updated
- 2026-11-02
OSVCLSA-2026-1793107254 No score
TuxCare security update for virtualenv (8 CVEs)
TuxCare rebuilt virtualenv to address 8 CVEs. Fixed in 20.39.1.post5+tuxcare.
- Record
CLSA-2026-1793107254- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
virtualenvPyPI |
before 21.14.9 | 21.14.9 |
python-virtualenvAzure Linux:3 |
before 20.36.1-10 | 20.36.1-10 |
virtualenvBitnami |
before 21.14.9 | 21.14.9 |
virtualenvTuxCare:PyPI From an advisory covering 8 CVEs |
20.39.1 up to 20.39.1.post5+tuxcare, plus listed versions 20.39.1.post1+tuxcare20.39.1.post2+tuxcare20.39.1.post3+tuxcare20.39.1.post4+tuxcare |
20.39.1.post5+tuxcare |
python-virtualenvDebian:12 |
All versions, plus listed versions 20.17.1+ds-120.19.0+ds-120.21.0+ds-120.23.0+ds-120.23.0+ds-2 Show 28 moreShow less20.24.1+ds-120.24.6+ds-120.24.6+ds-220.25.0+ds-120.25.0+ds-220.25.1+ds-120.26.1+ds-120.26.2+ds-120.26.6+ds-120.27.0+ds-120.28.0+ds-120.29.1+ds-120.29.3+ds-120.30.0+ds-120.30.0+ds-220.30.0+ds-320.31.2+ds-120.33.1+ds-120.34.0+ds-120.35.3+ds-120.35.4+ds-120.36.1+ds-120.38.0+ds-121.2.0+ds-121.2.4+ds-121.3.1+ds-121.4.1+ds-121.5.1+ds-1 |
None known |
python-virtualenvDebian:13 |
All versions, plus listed versions 20.31.2+ds-120.31.2+ds-1+deb13u120.33.1+ds-120.34.0+ds-120.35.3+ds-1 Show 8 moreShow less20.35.4+ds-120.36.1+ds-120.38.0+ds-121.2.0+ds-121.2.4+ds-121.3.1+ds-121.4.1+ds-121.5.1+ds-1 |
None known |
python-virtualenvDebian:14 |
All versions, plus listed versions 20.31.2+ds-120.33.1+ds-120.34.0+ds-120.35.3+ds-120.35.4+ds-1 Show 7 moreShow less20.36.1+ds-120.38.0+ds-121.2.0+ds-121.2.4+ds-121.3.1+ds-121.4.1+ds-121.5.1+ds-1 |
None known |
virtualenvPyPI |
before 21.14.9, plus listed versions 0.80.8.10.8.20.8.30.8.4 Show 45 moreShow less0.90.9.10.9.21.01.11.101.10.11.111.11.11.11.21.11.31.11.41.11.51.11.61.21.31.3.11.3.21.3.31.3.41.41.4.11.4.21.4.31.4.41.4.51.4.61.4.71.4.81.4.91.4rc11.51.5.11.5.21.61.6.11.6.21.6.31.6.41.71.7.11.7.1.11.7.1.21.7.21.8 |
21.14.9 |
python-virtualenvUbuntu:14.04:LTS |
All versions, plus listed versions 1.10.1-11.11-11.11-21.11.4-11.11.4-1ubuntu1 |
None known |
python-virtualenvUbuntu:16.04:LTS |
All versions, plus listed versions 1.11.6+ds-114.0.5+ds-114.0.5+ds-214.0.5+ds-415.0.0+ds-1 Show 6 moreShow less15.0.0+ds-215.0.1+ds-115.0.1+ds-215.0.1+ds-315.0.1+ds-3ubuntu115.0.1+ds-3ubuntu1.1 |
None known |
Show 6 moreShow less
| Package | Affected | Fixed |
|---|---|---|
python-virtualenvUbuntu:18.04:LTS |
All versions, plus listed versions 15.1.0+ds-115.1.0+ds-1.1 |
None known |
python-virtualenvUbuntu:Pro:20.04:LTS |
All versions, plus listed versions 15.1.0+ds-215.1.0+ds-2build115.1.0+ds-2ubuntu120.0.13-120.0.17-120.0.17-1ubuntu0.320.0.17-1ubuntu0.420.0.17-1ubuntu0.4+esm1 |
None known |
python-virtualenvUbuntu:Pro:22.04:LTS |
All versions, plus listed versions 20.4.6+ds-220.10.0+ds-120.12.1+ds-120.13.0+ds-120.13.0+ds-220.13.0+ds-2ubuntu0.1~esm1 |
None known |
python-virtualenvUbuntu:Pro:24.04:LTS |
All versions, plus listed versions 20.24.1+ds-120.24.6+ds-120.24.6+ds-220.25.0+ds-120.25.0+ds-220.25.0+ds-2ubuntu0.1~esm1 |
None known |
python-virtualenvUbuntu:26.10 |
All versions, plus listed versions 20.36.1+ds-121.4.1+ds-121.5.1+ds-1 |
None known |
python-virtualenvUbuntu:26.04:LTS |
All versions, plus listed versions 20.31.2+ds-120.35.3+ds-120.35.4+ds-120.36.1+ds-1 |
None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected | OSV | |
| affected | ||
| facts | OSV | |
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-113721 is a medium vulnerability (CVSS 4.5) in Pypa Virtualenv, published October 20, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.14%, the 3rd percentile. Fixed in virtualenv 21.14.9.
References
github.com/pypa/virtualenv/security/advisories/GHSA-558c-9hv2-4prj
github.com/pypa/virtualenv/pull/3431
github.com/pypa/virtualenv/commit/d25f28699eefb1fc34a251e5adf2b5de844c36cd
github.com/pypa/virtualenv/releases/tag/21.14.9
nvd.nist.gov/vuln/detail/CVE-2026-113721
github.com/advisories/GHSA-558c-9hv2-4prj
Left out here: 254 versions and the rest of 2 source descriptions. The JSON record has them.