Skip to main content
VulnerabilityPublished

CVE-2026-113715: virtualenv · Venv configuration readback can reinterpret path boundaries

Affects toxSource GCVE Database

0
Authoritative record:CVE-2026-113715· Low 2.0

Not known to be exploited · CVSS 2.0 Low : upgrade to 4.67.0 when convenient.

Data as of 27 Oct 2026, 06:13 UTC

CVSS 4.0 base score
2.0Low
How severe the flaw is on the 0–10 CVSS scale.
Patch
Available – fixed in 4.67.0
CVSS:4.0/AV:L/AC:H/AT:P/PR:N/UI:P/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N

Exploitation evidence

  1. Disclosed: evidence found.

    CVE published 20 Oct 2026

  2. Detectable: no evidence on record.

    No public nuclei template indexed

  3. Weaponised: no evidence on record.

    No public exploit code indexed in Metasploit or Exploit-DB

  4. Exploited in the wild: no evidence on record.

    Not in CISA KEV and no source reports exploitation

  5. Observed by OffSeq Mirage: no evidence on record.

    No attack traffic recorded by OffSeq Mirage sensors

A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), the Metasploit, Exploit-DB and nuclei-templates index as of 26 Oct 2026.

What to do

Upgrade tox to 4.67.0.

Affected software

ProductAffectedFixed in
Homebrewtoxpkg:brew/toxOther threatsAffected
  • >=3.27.0 <4.67.0
Fixed in
  • 4.67.0

Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.

Description

## Summary The venv creator reads the standard library's generated pyvenv.cfg with splitlines() and merges all resulting keys over virtualenv's configuration. Line-boundary characters from destination data can therefore become configuration structure before output sanitization. Anthropic's OSS Scanner reported this as `ANT-2026-6ZQHJ6VI` in `virtualenv-20261015-005`, received on 15 October 2026. ## Affected area Configuration readback by the stdlib venv creator. ## Attacker-controlled input An untrusted caller must influence the destination pathname passed to the venv creator. That pathname must contain a character that Python treats as a line boundary. The builtin creator follows a different configuration-writing path. ## Affected code The relevant 21.14.8 source is in [venv readback](https://github.com/pypa/virtualenv/blob/a795ab116356afde11039cf2763d77e8a06da738/src/virtualenv/create/via_global_ref/venv.py), [configuration parser](https://github.com/pypa/virtualenv/blob/a795ab116356afde11039cf2763d77e8a06da738/src/virtualenv/create/pyenv_cfg.py). ## Mechanism With `--creator venv`, virtualenv asks the standard library to create an environment, reads its `pyvenv.cfg` with `str.splitlines()`, and merges the parsed keys over virtualenv's chosen settings. CPython writes the destination into a `command =` value without escaping every character that `splitlines()` recognizes as a line boundary. A destination containing U+2028, for example, can therefore supply new `home` and `include-system-site-packages` keys during readback. Virtualenv writes those keys as real configuration lines. The builtin creator does not use this readback path. ## Reproduction and evidence The scanner used one destination value with U+2028 between `home = /attacker/chosen`, `include-system-site-packages = true`, and an absorbing `x =` key. A `python -m venv --without-pip` control kept the value inside its `command` line and reported the base interpreter's `sys._home`. `virtualenv --creator venv --no-seed` on the same value wrote separate injected configuration lines and its interpreter reported `sys._home = /attacker/chosen`. The path did not require a literal newline byte. The control establishes that the extra keys came from virtualenv's readback, not from the standard library's initial file alone. The altered `home` can change which standard-library files the interpreter loads if an attacker also controls that location; `include-system-site-packages = true` weakens environment isolation. Choosing the venv creator is required, either explicitly or through a rare fallback. The report traces the readback to the 20.x rewrite, first released in 20.0.0b1. [PR #3426](https://github.com/pypa/virtualenv/pull/3426) rejects line-boundary characters before creation and rebuilds the final configuration from selected settings. ## Attack sequence ```mermaid sequenceDiagram participant A as Destination supplier participant S as stdlib venv participant V as virtualenv readback participant C as Final pyvenv.cfg participant P as Environment Python A->>S: path contains U+2028 and home = /attacker/chosen S->>C: write path inside command value V->>C: read with splitlines() V->>V: parse suffix as separate keys V->>C: rewrite home and include-system-site-packages P->>C: read injected keys on startup C-->>P: sys._home changes and isolation is disabled ``` ## Reproducer files The following files are from the scanner report for `ANT-2026-6ZQHJ6VI`. 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 included as evidence and were not rerun for this advisory update. **`repro`** ```text #!/usr/bin/env python3 import os import shutil import subprocess work = "/tmp/repro-venv-creator-cfg" shutil.rmtree(work, ignore_errors=True) # the attacker's input: the text of a destination path. # U+2028 ends a line for str.splitlines() but not for CPython's own pyvenv.cfg reader. # The slashes only make nested directories; the last key, x, absorbs the rest of the path. LS = "\u2028" dest = f"{work}/proj{LS}home = /attacker/chosen{LS}include-system-site-packages = true{LS}x = /env" base_python = os.path.realpath(shutil.which("python")) real_home = os.path.dirname(base_python) def pyvenv_cfg_lines(env): # split on "\n" only, as the interpreter does return open(f"{env}/pyvenv.cfg", encoding="utf-8").read().split("\n") def sys_home(env): # site.py sets sys._home from the `home` key of the environment's pyvenv.cfg probe = [f"{env}/bin/python", "-c", "import sys; print(sys._home)"] return subprocess.run(probe, capture_output=True, text=True, check=True).stdout.strip() # control: the same path through the standard library alone stdlib_env = dest + "-stdlib"

References

Community reviews

Loading reviews…