GHSA-6w3r-xr62-4xcp: OS Command Injection
Summary
Caller-supplied prompt text can become executable syntax in the bash activation prompt. An attacker must influence the value passed to virtualenv, and a user must subsequently activate the environment and draw the prompt.
Affected area
Bash runs with compatibility level 4.2 and draws the environment prompt. Its replacement-string behavior differs from newer compatibility modes, leaving prompt text subject to a second interpretation. The report concerns POSIX activation. It does not establish exposure through unrelated activators.
Attacker-controlled input
The attacker controls prompt or destination data passed as a virtualenv argument. The user later sources the generated activation script.
Affected code
The relevant 21.14.8 source is in bash prompt assembly.
Mechanism
bin/activate copies the prompt into PS1. A shell expands PS1 again when it draws a prompt, so the value must survive both the assignment and that later expansion. The bash and zsh branches escape for their usual prompt rules. Three other paths still interpret prompt data as commands: dash sourcing activate directly, a child dash inheriting an exported PS1 from bash, and bash running at compatibility level 4.2 or lower. A value such as x$(touch MARKER)y then runs touch on prompt display. Creating the environment does not run the payload.
Reproduction and evidence
The scanner's child-shell reproducer creates an environment with --prompt 'x$(touch MARKER)y', sources bin/activate in interactive bash, and starts interactive sh (dash). Its control leaves PS1 unexported: neither shell creates MARKER. With PS1 exported, bash displays the prompt without executing the value, but the child dash creates MARKER; the reproducer ends in BUG. The report gives separate console reproductions for direct dash activation and bash compatibility level 4.2. The child-shell path can arise after another environment's activation has exported PS1.
The scanner linked this finding to #3326 and its follow-up #3390. Those changes cover the usual bash path but leave the three paths above. The proposed change in #3418 keeps a variable reference in PS1, so prompt text is expanded as data when drawn. The private report's reproducer was run by the scanner; the later command-execution result was supplied by the maintainer review. This advisory does not claim that every POSIX shell or every bash mode is affected.
Introduced
The report traces direct dash sourcing to the first 20.x activator (20.0.0b1), and inherited PS1 to #3390 (first released in 21.14.7). It demonstrates the compatibility-level case on bash 5.2. Those historical boundaries come from the report; the release confirmed by the maintainer is 21.14.8.
Attack sequence
mermaid sequenceDiagram participant A as Prompt supplier participant V as virtualenv participant B as Interactive bash participant D as Child dash A->>V: --prompt x$(touch MARKER)y V->>B: generated bin/activate B->>B: assign escaped prompt to PS1 alt PS1 is not exported B->>D: start sh with its own PS1 D-->>B: marker absent else PS1 is exported B->>D: start sh with inherited PS1 D->>D: expand $(touch MARKER) on prompt draw D-->>B: marker created end
Reproducer files
The following files are from the scanner report for ANT-2026-1MOI2X3T. 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 shutil import subprocess from pathlib import Path
work = Path("/tmp/repro-inherited-ps1") shutil.rmtree(work, ignoreerrors=True) work.mkdir() marker = work / "MARKER" print("sh is", Path("/bin/sh").resolve())
attacker: only the text a tool such as tox or hatch relays as --prompt hostileprompt = f"x$(touch {marker})y"
set up: the environment, made with the virtualenv command subprocess.run( ["virtualenv", "--no-periodic-update", "--without-pip", "--activators", "bash", "--prompt", hostileprompt, str(work / "env")], check=True, stdout=subprocess.DEVNULL, ) assert not marker.exists(), "creating the environment must not run the prompt"
user: in an interactive bash, source bin/activate, then start sh. -i makes both shells draw their prompt although stdin is not a terminal; sh draws one, reads EOF and returns session = Path("/tmp/repro.d/usersession.sh").readtext() interactivebash = ["bash", "--norc", "--noprofile", "-i"]
control: PS1 set in bash but not exported, so sh does not inherit it control = subprocess.run(interactivebash, input="PS1='\\$ '\n" + session, stderr=subprocess.PIPE, text=True, check=True) print(control.stderr) ranincontrol = marker.exists() print("CONTROL PS1 not exported: marker created:", ranincontrol)
attack: the same session with the one difference that PS1 is exported attack = subprocess.run(interactivebash, input="export PS1='\\$ '\n" + session, stderr=subprocess.PIPE, text=True, check=True) print(attack.stderr) raninattack = marker.exists() print("OBSERVED PS1 exported: marker created:", raninattack)
check: the prompt text must be drawn, never run, by every shell that draws it print("BUG" if raninattack and not ranincontrol else "OK")
repro.d/usersession.sh
text . /tmp/repro-inherited-ps1/env/bin/activate sh -i </dev/null
Impact
An affected activation can execute commands with the user's privileges. Severity is high, following the generated-script precedent in GHSA-p58f-9548-mpm2. The attack requires control of argument data and subsequent user activation; it does not require replacing an executable or controlling app-data contents.
Versions tested
The supplied follow-up review marks command execution as independently confirmed by its author. The implementation review used source inspection and patched literal-value checks, without rerunning that exploit. The relevant activation source is identical between release 21.14.8 and upstream/main 233359cb. The maintainer previously confirmed validation of the reported findings on 21.14.8. The first affected version has not been established. Fixed in 21.14.9.
Known mitigation
Use trusted prompt and destination values. On older releases, disable the environment prompt with VIRTUALENVDISABLEPROMPT=1. Recreate environments with virtualenv 21.14.9 or later.
Affected release
- [x] The problem reproduces on virtualenv 21.14.8.
Credits
The original report was supplied by Anthropic's OSS Scanner as ANT-2026-1MOI2X3T. The subsequent maintainer-provided review identified the remaining activation path.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
pip/virtualenvto a version that resolves this vulnerability.Fixed in 21.14.9 - Upgrade
Upgrade
virtualenvto a version that resolves this vulnerability.Fixed in 21.14.9 - Configuration
Disable the environment prompt on older releases by setting VIRTUAL_ENV_DISABLE_PROMPT=1.
virtualenv activation VIRTUAL_ENV_DISABLE_PROMPT = 1 - Compensating control
Use trusted prompt and destination values when invoking virtualenv.
Event History
Frequently Asked Questions
Who is realistically exposed?
Exposure requires a user to source a generated POSIX activation script in Bash running at compatibility level 4.2, then have the shell draw the environment prompt. The attacker must also be able to influence prompt or destination data supplied to virtualenv.
What must an attacker do to trigger the issue?
The attacker needs control over prompt or destination data passed as a virtualenv argument. A user must later source the generated bin/activate script and draw a prompt, causing PS1 to undergo a later interpretation.
Does this report establish exposure through every virtualenv activator?
No. The report concerns POSIX activation and does not establish exposure through unrelated activators.
How can I identify potentially affected environments?
Identify environments whose activation scripts were generated from caller-controlled prompt or destination values, especially where users activate them under Bash compatibility level 4.2. The relevant behavior is in bin/activate, which copies the prompt value into PS1.