# virtualenv activate.fish runs commands from a path or prompt with a backslash before a quote

virtualenv's activate.fish quotes values with POSIX shlex.quote, so a backslash-before-quote in the env path or prompt escapes fish's single quotes and executes command substitution on source.

- Payout: Research
- Severity: high
- Program: virtualenv
- Found by: Kwstubbs
- Disclosed: 2026-10-21
- CVE: CVE-2026-113732
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-c947-3pg5-gm8q
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-c947-3pg5-gm8q
- Tags: area:oss domain:oss impact:rce kind:research platform:ghsa sev:high tech:python technique:parser-differential technique:source-review vuln:command-injection

**Target:** generated activate.fish shell activation script (VIRTUAL_ENV / VIRTUAL_ENV_PROMPT)
**Root cause:** FishActivator inherits ViaTemplateActivator.quote which uses shlex.quote (POSIX sh rules); fish treats backslash inside single quotes as an escape, so a backslash before a quote terminates the literal early and exposes following command substitution.
**Steps:**
1. Create env with `virtualenv --prompt "x\'\$(touch PWNED)'\'" venv` (or name the directory with the same payload).
2. Inspect generated `venv/bin/activate.fish`: `set -gx VIRTUAL_ENV_PROMPT 'x\'"'"'$(touch PWNED)'"'"'\'"'"''`.
3. Run `source venv/bin/activate.fish` in fish; `touch PWNED` executes with no error.
4. Note `--prompt .` derives the prompt from the current directory name, so a hostile directory name alone triggers it.
**How found:** Noticed FishActivator defines no quote() and inherits shlex.quote; reasoned that fish's single-quote escaping differs from POSIX sh for backslash sequences, then crafted a backslash-before-quote payload to break out of the literal.
**Exploitation:** Attacker controls the environment path or prompt (CI job/branch/project name passed through a wrapper, or a directory name in a cloned repo/archive). Victim sources the generated activate.fish in fish; the embedded `$(...)` command substitution runs with the victim's privileges.
**Insight:** When a tool emits shell-specific scripts but reuses a generic POSIX quoter, diff the target shell's quoting rules (fish treats \' and \\ inside single quotes as escapes) and test payloads with a backslash immediately before a quote; also check prompt defaults like `--prompt .` that pull in attacker-controllable directory names.

## Proof of concept (copied exactly from the original report)

**Generated activate.fish line with injected command substitution**

```
set -gx VIRTUAL_ENV_PROMPT 'x\'"'"'$(touch PWNED)'"'"'\'"'"''
```

**Prompt payload that triggers command execution**

```
--prompt "x\'\$(touch PWNED)'\'"
```

