Repository navigation
馃悰 fix(create): reject ambiguous venv paths - #3426
Merged
gaborbernat merged 5 commits intoOct 17, 2026
Merged
Conversation
Reported by Anthropic's OSS Scanner: ANT-2026-6ZQHJ6VI.
Check line boundaries before pathname inspection, which raises an OS error for invalid Windows names on older Python versions. Validate the resolved destination as well before invoking stdlib venv.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The stdlib
venvcreator (--creator venv) runs CPython'svenvmodule to lay out the environment and then writes virtualenv's ownpyvenv.cfgover the onevenvleft.venvrecords its command line in that file as thecommandkey, destination included (create_configuration).pyvenv.cfgis a line-based format with no escaping, so a destination that contains a line boundary (any of the ten charactersstr.splitlinesrecognizes) ended thecommandline early and left the rest of the destination as a line of its own. virtualenv then read that file back and merged every line it parsed into the final configuration, so a destination such asenv\nhome = /elsewhereset the environment'shome, the key the interpreter reads at startup to find its base installation. 馃悰 After this PR thevenvcreator rejects a destination with a line boundary before it touches the filesystem, and the finalpyvenv.cfgis built from the selected interpreter and virtualenv's own options alone. This addresses draft advisory ANT-2026-6ZQHJ6VI.Destination text re-entered the configuration through the stdlib file
Venv.set_pyenv_cfgran aftervenvhad written its file. It calledrefresh, whose parser splits each line at its first=, let the base class rebuild virtualenv's own content, and laid the parsed stdlib values over it withupdate, under the comment "prefer venv options over ours". This order dates from the creator's first version. The text after a boundary in the destination therefore became a key and a value that replaced virtualenv's own,homeincluded.writecollapses line boundaries in each key and value, so the injected pair came out as a well-formed line of the final file.set_pyenv_cfgno longer reads the stdlib file. The content comes fromCreator.set_pyenv_cfg, which starts from an empty mapping and fillshome,implementation, the version keys,executable,command,virtualenvandpromptfrom the selected interpreter and the options, and fromViaGlobalRefApi.set_pyenv_cfg, which addsinclude-system-site-packages. GraalPy's launchers readvenvlauncher_commandandbase-executablefrompyvenv.cfg; those two values used to arrive through the merge and are now set from the selected interpreter'ssystem_exe.flowchart LR V[venv writes pyvenv.cfg with command = ... dest] --> R[refresh parses every line at its first =] R --> U[update lays the parsed keys over virtualenv's own] U --> W[write collapses boundaries, injected line kept] S[Creator.set_pyenv_cfg from interpreter and options] --> G[GraalPy launcher keys from system_exe] G --> X[write] classDef bad fill:#ffd6d6,stroke:#c1121f,color:#5c0000; classDef ok fill:#d8f3dc,stroke:#2d6a4f,color:#1b4332; class V,R,U,W bad; class S,G,X ok;Validation checked the path only after it had touched the filesystem
Creator.validate_destchecks that the filesystem codec can encode the destination, asksPath.existswhether a file sits there, resolves the path withPath.resolve, which follows symbolic links, refusesos.pathsepin the raw and the resolved value (the resolved check comes from #3399), and walks up to a writable parent. No check covered line boundaries: a file name holding one is legal on POSIX, and thevenvcreator was the only one that let it reach a line-based file unescaped.Venv.validate_destnow runs the boundary check on the raw value beforesuper().validate_destand again on the resolved value it returns. The first check precedes every filesystem call, so a rejected destination leaves nothing behind. The second catches a symbolic link along the path whose target name holds a boundary. Both raiseArgumentTypeError, which argparse reports as a usage error with exit code 2. Other creators keep their destination support, since their configuration never passes through the stdlib file.flowchart LR A[raw destination] --> B{line boundary?} B -- yes --> E1[ArgumentTypeError, no filesystem access] B -- no --> C[Creator.validate_dest: codec, exists, resolve, pathsep, writable parent] C --> D{boundary in the resolved path?} D -- yes --> E2[ArgumentTypeError] D -- no --> F[create] C -. before .-> O[create, venv writes the destination into pyvenv.cfg] classDef bad fill:#ffd6d6,stroke:#c1121f,color:#5c0000; classDef ok fill:#d8f3dc,stroke:#2d6a4f,color:#1b4332; class O bad; class E1,E2,F ok;Behavior changes
A
venvcreator destination that contains any of the ten line-boundary characters fails with an argument error before virtualenv reads or creates anything. The finalpyvenv.cfgholds virtualenv's keys only; keys the stdlibvenvwrote, vendor-specific ones included, are no longer merged. On GraalPy,venvlauncher_commandandbase-executableare set to the selected interpreter's executable. Thehomevalue of macOS framework builds and Windows Store installs now comes from virtualenv's own computation instead of the stdlib file; this PR's tests do not cover those builds. Ordinary destination paths remain supported.Reported by Anthropic's OSS Scanner: ANT-2026-6ZQHJ6VI.