{"id":"PYSEC-2026-4012","summary":"virtualenv writes prompt values into pyvenv.cfg without sanitizing line boundaries, allowing configuration injection","details":"`pyvenv.cfg` is a line-based format with no escape syntax. `PyEnvCfg.write()`\nwrote values verbatim, while `PyEnvCfg._read_values()` parses the file with\n`str.splitlines()`. A value containing a line boundary therefore became\nadditional configuration lines, and because reading is last-wins, the injected\nkeys replaced any key written earlier in the file.\n\nThe `prompt` value is the reachable input: it is set by `--prompt`, by the\n`VIRTUALENV_PROMPT` environment variable, or from the config file, and\n`write()` emits `prompt` before `home`. A crafted prompt can therefore set\n`home` in the generated `pyvenv.cfg`. `home` is what tooling reads to locate\nthe base interpreter, so a consumer that trusts it can be pointed elsewhere.\n`implementation`, `version_info`, `version`, `executable`, `command` and\n`virtualenv` are also written before `prompt` and can be replaced the same\nway.\n\nThis requires the prompt to come from somewhere other than the person running\nthe command, for example a CI job templating a branch name into it, tooling\nderiving an environment name from user-supplied data, or an inherited\n`VIRTUALENV_PROMPT`.\n\nThe boundary set is the one `str.splitlines()` recognizes, which is wider than\n`\\n`: `\\r`, `\\v`, `\\f`, the file, group and record separators, `U+0085`,\n`U+2028` and `U+2029` were all written through unchanged and all split the\nline when read back.\n\nFixed in 21.7.11: `PyEnvCfg.write()` now collapses those boundaries to spaces\nas it serializes each line, so it cannot emit a structurally invalid file\nregardless of what a caller places in `content`.\n","aliases":["CVE-2026-102938","GHSA-9h9j-4vrj-gf7g"],"modified":"2026-09-30T16:45:02.392267841Z","published":"2026-09-17T16:42:47Z","references":[{"type":"WEB","url":"https://github.com/pypa/virtualenv/security/advisories/GHSA-9h9j-4vrj-gf7g"},{"type":"WEB","url":"https://github.com/pypa/virtualenv/pull/3247"},{"type":"FIX","url":"https://github.com/pypa/virtualenv/commit/a30f995461043acb6cacbf3a890951563ccf7140"},{"type":"WEB","url":"https://github.com/pypa/virtualenv/releases/tag/21.7.11"},{"type":"PACKAGE","url":"https://github.com/pypa/virtualenv"},{"type":"PACKAGE","url":"https://pypi.org/project/virtualenv"}],"affected":[{"package":{"name":"virtualenv","ecosystem":"PyPI","purl":"pkg:pypi/virtualenv"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"21.7.11"}]}],"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.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.2","21.7.3","21.7.4","21.7.5","21.7.6","21.7.7","21.7.8","21.7.9"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/virtualenv/PYSEC-2026-4012.yaml"}}],"schema_version":"1.9.0","credits":[{"name":"Bernát Gábor","type":"FINDER"}]}