Oct 24, 2026·5 min read·6 visits
Legacy multibyte encodings swallow escaping backslashes in virtualenv xonsh activation scripts, leading to shell literal breakout and code injection.
A code injection vulnerability in pypa/virtualenv prior to version 21.14.9 allows arbitrary command execution in xonsh shells using legacy multibyte encodings when sourcing activate.xsh.
pypa/virtualenv is a foundational component of the Python ecosystem used to generate isolated virtual environments. When initializing an environment, virtualenv writes shell-specific activation scripts such as activate.xsh for xonsh shells. These scripts embed metadata including environment destination paths and custom prompt titles supplied during creation.
Prior to version 21.14.9, virtualenv formatted prompt names and path literals using Python's repr() function inside XonshActivator.quote(). This implementation allowed non-ASCII UTF-8 characters to be formatted directly into generated activation scripts.
When xonsh parses the resulting script on systems configured with legacy multibyte character encodings (such as GBK, CP932, or Big5 via $XONSH_ENCODING), character decoding misalignment occurs. An attacker who controls virtual environment prompts or installation target directory names can inject arbitrary xonsh or Python code that executes when the target user sources activate.xsh.
The vulnerability resides in string literal quoting within XonshActivator.quote(). The xonsh shell treats activate.xsh activation files as executable Python source code. To escape dynamic values embedded in template files, XonshActivator.quote() relied on repr(string).
When repr() processes non-ASCII strings in Python 3, it outputs printable non-ASCII characters directly as raw UTF-8 bytes while escaping single quotes and special control bytes using a backslash (\, byte 0x5C). If xonsh executes on a system where $XONSH_ENCODING specifies a multibyte encoding, it decodes activate.xsh using that multibyte decoder rather than UTF-8.
In multibyte character encodings like GBK, certain non-ASCII UTF-8 character byte sequences contain trailing bytes equal to 0x5C. The legacy decoder combines the preceding character byte with the 0x5C backslash byte, processing both as a single double-byte character. Consequently, the backslash is swallowed during decoding and fails to escape the subsequent quote (0x27). The string literal terminates prematurely, causing remaining input bytes to be evaluated as arbitrary statements.
In src/virtualenv/activation/xonsh/__init__.py, the original static method quote() delegated string quoting directly to repr():
# Unpatched implementation
@staticmethod
def quote(string: str) -> str:
"""Quote as a Python literal — xonsh parses the activation script as Python."""
return repr(string)To resolve this vulnerability, commit e5fc2e6e189a877ff559af288919e3525d13e5e0 replaced repr(string) with ascii(string):
# Patched implementation
@staticmethod
def quote(string: str) -> str:
"""Keep literals independent of the encoding xonsh uses to read the script."""
return ascii(string)By utilizing ascii(), all non-ASCII characters are explicitly converted into 7-bit ASCII escape sequences (such as \uXXXX or \xXX). Because the generated output consists exclusively of 7-bit ASCII bytes (0x00–0x7F), no character byte can be paired as a multibyte lead byte in encodings like GBK or Big5. This eliminates backslash-swallowing and secures literal boundaries across all target decoders.
Exploitation requires supplying a crafted non-ASCII string as either the virtual environment target path or the --prompt parameter during environment creation.
When virtualenv builds the environment, XonshActivator.quote() writes the input string containing non-ASCII characters into activate.xsh. The input sequence is chosen such that its raw UTF-8 byte sequence ends in a byte that pairs with 0x5C when interpreted under GBK.
When the victim sources activate.xsh inside a xonsh session configured with a legacy $XONSH_ENCODING, the multibyte decoder consumes the backslash as part of a double-byte character. The quote character remains unescaped, breaking out of the path or prompt literal. Subsequent bytes are interpreted directly as executable xonsh commands with the permissions of the user.
Successful exploitation of CVE-2026-113718 grants arbitrary code execution in the shell context of the user sourcing the activate.xsh script. The vulnerability carries a CVSS v4.0 base score of 7.1 (High) with vector string 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.
Exploitation depends on specific preconditions, including local script execution and active multibyte legacy encodings in $XONSH_ENCODING. However, the direct confidentiality, integrity, and availability impacts are high if triggered within vulnerable user sessions.
No active exploitation has been recorded in CISA KEV, and the EPSS score is 0.00142 (3.23 percentile). Nonetheless, development environments utilizing xonsh in non-UTF-8 locales remain exposed until updated.
To remediate CVE-2026-113718, upgrade virtualenv to version 21.14.9 or higher:
pip install --upgrade virtualenvUpgrading the package does not retroactively modify existing activate.xsh files on disk. Previously generated virtual environments must be re-created or regenerated using patched virtualenv releases.
To audit disk storage for vulnerable files, check activate.xsh scripts for unescaped, non-ASCII UTF-8 bytes in variable definitions. Patched scripts represent non-ASCII input exclusively via ASCII escape sequences (e.g., \uXXXX).
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| Product | Affected Versions | Fixed Version |
|---|---|---|
virtualenv pypa | < 21.14.9 | 21.14.9 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-94 |
| Attack Vector | Local |
| CVSS v4.0 Score | 7.1 (High) |
| EPSS Score | 0.00142 (3.23 percentile) |
| Vulnerability Impact | Code Injection / Command Execution |
| Exploit Availability | No Public Exploit |
| CISA KEV Status | Not Listed |
The software constructs all or part of a code segment using externally-influenced input, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
A command injection vulnerability in PyPA virtualenv prior to version 21.14.9 allows local attackers to execute arbitrary commands when a user sources a generated shell activation script containing a crafted prompt value.
virtualenv prior to version 21.14.9 contains a local permission inheritance vulnerability in its app-data copy seeder. Due to the reliance on Python's standard shutil.copy() function, permissions from cached packages are replicated directly onto newly created virtual environment files, ignoring the active process umask and allowing unprivileged local attackers to inject code.
Prior to version 21.14.9, virtualenv downloads wheel files directly into its reusable persistent cache directory prior to verifying PyPI digests or compatibility. Combined with unsanitized stdout parsing from pip, a controlled package index can cause virtualenv to calculate digests against pre-existing local files while leaving unverified archives in the shared persistent cache.
A verification bypass vulnerability in virtualenv allows unverified Python wheel installation when using pip seeding with the download flag. Affected versions fail to perform SHA-256 digest validation against PyPI metadata before invoking pip.
Insecure temporary file usage (CWE-377) in virtualenv app-data seeder creates dangling symlinks pointing to deleted temporary directories, allowing local attackers on shared systems to execute arbitrary code.
A Time-of-Check Time-of-Use (TOCTOU) race condition in the Python `filelock` library (versions 4.1.0 to 4.2.0) allows local unprivileged attackers to cause arbitrary directory deletion by swapping file system targets during lock cleanup or dead owner sweeps.