GitHub Advisoryadvisory · PyPI

GHSA-cx6f-vjhg-g3cc virtualenv: Bash activator leaves the --prompt value unescaped in PS1 when promptvars is off, so prompt expansion later executes it (regression in 21.14.9)

HIGH Published 2026-10-23 Modified 2026-10-23 Official page ↗
Aliases
CVE-2026-115586
Ecosystem
PyPI
Packages
virtualenv
Severity
High
CVSS vector
CVSS:4.0/AV:L/AC:H/AT:P/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
Weaknesses
CWE-78
Reviewed
2026-10-23

Affected packages

• PyPI virtualenv: >= 21.14.9, < 21.14.13 → fixed in 21.14.13

Description

Summary The bash activator puts the caller-supplied prompt value into `PS1` unescaped whenever `promptvars` is off at activation time, and assumes prompt expansion can never run afterwards. `promptvars` and posix mode are runtime shell state, not a property of the activation moment: re-enabling prompt expansion in the same shell (`shopt -s promptvars`, `set -o posix`) or starting an interactive child bash that inherits the exported `PS1` makes bash expand the stored value and run the command substitution in it. An attacker who controls the prompt value gets command execution as the user who sources `bin/activate`. This is a regression. 21.14.8 escaped `$` and the backtick unconditionally and stays inert under the same sequence. The 21.14.9 fix for GHSA-6w3r-xr62-4xcp (#3418) rewrote that block and left bash with a path that stores the raw value whenever `promptvars` is off. Affected area Activation scripts (activate, activate.bat, activate.csh, activate.fish, activate.nu, Activate.ps1, activate.xsh) — specifically the bash `bin/activate`. Attacker-controlled input The `--prompt` value, which the security policy already lists as a value that may come from project metadata the user did not write. It reaches `virtualenv` as `--prompt` on the command line or as the `prompt` argument of the API, is written into `bin/activate` as the literal `VIRTUAL_ENV_PROMPT` assignment (correctly quoted, the value arrives there as data), and the script then copies it into `PS1` without escaping `$` or the backtick. Example: a repository or tool config supplies a prompt such as `x$(id > /tmp/ve-pwn)y`; the victim creates and later activates the environment. Details Source of the latest release: `src/virtualenv/activation/bash/activate.sh` lines 140-160 ```sh if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] && { [ -n "${BASH_VERSION-}" ] || [ -n "${ZSH_VERSION-}" ]; }; then _OLD_VIRTUAL_PS1="${PS1-}" # bash expands PS1 after decoding its prompt escapes when promptvars is on or in posix mode, so the prompt holds a # reference to the variable and its value never reaches that expansion; otherwise only the decode runs and # backslashes need doubling. ... if [ -n "${BASH_VERSION-}" ] && { shopt -q promptvars || shopt -qo posix; }; then PS1='(${VIRTUAL_ENV_PROMPT-}) '"${PS1-}" else _VIRTUAL_PROMPT="${VIRTUAL_ENV_PROMPT//\\/\\\\}" if [ -n "${ZSH_VERSION-}" ]; then _VIRTUAL_PROMPT="${_VIRTUAL_PROMPT//\$/\\\$}" _VIRTUAL_PROMPT="${_VIRTUAL_PROMPT//\`/\\\`}" _VIRTUAL_PROMPT="${_VIRTUAL_PROMPT//\%/%%}" fi PS1="(${_VIRTUAL_PROMPT}) ${PS1-}" unset _VIRTUAL_PROMPT fi fi ``` Two separate mistakes combine: 1. A runtime shell option is treated as a constant. `shopt -u promptvars` at the moment of `source` does not bind the shell forever: `shopt -s promptvars` and `set -o posix` both re-enable PS1 expansion in the same shell. The code itself tests `shopt -qo posix` because posix mode expands the prompt regardless of `promptvars`, which shows the expansion state is not fixed by the activation. 2. The escaping that would keep the value inert in that mode is missing for bash. GHSA-5vjq-rrrf-7h2q ("activate runs commands from the environment name in zsh with PROMPT_SUBST") fixed exactly this shape for zsh, reasoning that "a user can set it after activation", by escaping `$` and the backtick. Bash lost the equivalent escaping in the same commit that added the zsh branch. The rendered prompt shows the regression directly: ``` 21.14.8 PS1 = (x\\$(id > /tmp/ve-pwn)y) /tmp/ve-pwn)y) /tmp/ve-pwn)y) /tmp/ve-pwn)y' /tmp/venv-demo cat > /tmp/rc `\$`, backtick -> ``\` ``, `\` -> `\\`). Verified on bash 5.1.16 that a `PS1` built that way is inert with `promptvars` on, with `promptvars` off, and with `promptvars` off followed by posix mode, while still rendering the literal value on screen. That is better than both current shapes: 21.14.8's four-level escaping left stray backslashes on screen, which is why the block was rewritten. ```sh else _VIRTUAL_PROMPT="${VIRTUAL_ENV_PROMPT//\\/\\\\}" _VIRTUAL_PROMPT="${_VIRTUAL_PROMPT//\$/\\\$}" _VIRTUAL_PROMPT="${_VIRTUAL_PROMPT//\`/\\\`}" if [ -n "${ZSH_VERSION-}" ]; then …

Related

Alias · CVE (1)

  • CVE-2026-115586 virtualenv is a tool for creating isolated virtual python environments. vulnerability

Weakness · CWE (1)

  • CWE-78 Improper Neutralization of Special Elements used in an OS Command weakness

References