Skip to content

Pipenv venv discovery ignores the project's .env, so a PIPENV_CUSTOM_VENV_NAME or WORKON_HOME set there leaves the Pipenv venv unpatched, patches the system Python instead, and VEX attests not_affected #546

Description

[agent] Found by the scheduled Pipenv bug-hunt routine (ledger #313).

Summary

Pipenv loads the project's .env file before it resolves the virtualenv. Settings placed there, such as PIPENV_CUSTOM_VENV_NAME=myenv or WORKON_HOME=..., move the venv on every Pipenv release from 2022.12.19 to 2026.8.0 (pipenv --venv reports $WORKON_HOME/myenv). socket-patch's Pipenv venv discovery (python_crawler.rs) reads only the process environment, so it never finds that venv.

On main 61cfb9b, an agent-mode scan --apply:

In hosted mode the same shape loses the redirect_pypi_stale_install warning: the warm .env-named venv is never probed, so pipenv install --deploy silently keeps the unpatched bytes with no hint. Exporting the same variable in the shell instead of .env works correctly in both modes. That's the control.

Impact

  • A project that keeps Pipenv settings in .env stays vulnerable while socket-patch reports success, and VEX claims it's fixed (a false attestation).
  • Agent mode modifies the system interpreter's site-packages, which nobody asked for (or fails with Permission denied as non-root).
  • Hosted mode loses the stale-install guard.

Repro (Linux, real Pipenv, mock patch API serving a patched six 1.16.0)

export WORKON_HOME=$PWD/wh
mkdir proj && cd proj
printf '[packages]\nsix = "==1.16.0"\n' > Pipfile
echo 'PIPENV_CUSTOM_VENV_NAME=myenv' > .env      # control: `export PIPENV_CUSTOM_VENV_NAME=myenv` instead
pipenv install
pipenv --venv                                     # -> $WORKON_HOME/myenv
socket-patch scan --apply --json                  # status success, scannedPackages 115 (global interpreter)
pipenv run python -c "import six; print(getattr(six,'SOCKET_PATCHED',False))"   # False: venv unpatched
grep -c SOCKET_PATCHED /usr/lib/python3/dist-packages/six.py                     # 1: system copy patched
socket-patch vex --product pkg:pypi/app@1.0.0     # not_affected GHSA-...

WORKON_HOME=<dir> in .env (with nothing exported) behaves the same way: Pipenv creates <dir>/proj-<hash>, and socket-patch patches the system copy.

Hosted variant: the same project, then socket-patch scan --mode hosted --json. The lock is redirected, but no redirect_pypi_stale_install fires. pipenv install --deploy keeps the unpatched six. With the variable exported, the warning fires and names $WORKON_HOME/myenv/lib/python3.x/site-packages.

Expected vs actual

  • Expected: docs/testing/pipenv-compatibility.md says agent mode "patches the installed distribution in the venv Pipenv resolves for the project". It says the crawler "honours the .venv file pointer, PIPENV_CUSTOM_VENV_NAME, PIPENV_PIPFILE, WORKON_HOME …" so that a scan run outside pipenv run sees the project's venv. CLI_CONTRACT's "Pipenv stale-install guard" says the guard probes "Pipenv's out-of-tree WORKON_HOME venv". Pipenv takes those settings from .env as well as from the shell (unless PIPENV_DONT_LOAD_ENV is set). If reading .env is out of scope by design, then at minimum the venv-less fallback must not patch the system interpreter, and vex must not attest.
  • Actual: the .env-resolved venv is never probed. Agent mode patches the system Python, exits 0, and VEX says not_affected. Hosted mode gives no stale-install warning.

Matrix (Linux; agent scan --apply / pipenv run sees patched / vex)

Pipenv .env PIPENV_CUSTOM_VENV_NAME exported PIPENV_CUSTOM_VENV_NAME (control) .env WORKON_HOME
2022.12.19 (py3.10) fail: venv unpatched, system patched, vex not_affected pass untested
2023.12.1 (py3.11) fail (2/2) pass fail
2024.4.1 (py3.12) fail pass untested
2025.1.3 (py3.12) fail pass untested
2026.8.0 (py3.12) fail (2/2) pass fail

Hosted stale-install warning, .env vs exported: missing vs present on 2023.12.1 and 2026.8.0.

macOS / Windows: not probed. The routine's probe branches are blocked because branch deletion through the git proxy fails. The discovery code is OS-independent.

First bad version

Not a regression. The v4.0.0 release (PyPI socket-patch==4.0.0) also leaves the .env-named venv unpatched (2026.8.0, 2023.12.1). Since #388 / #504 the miss additionally patches the system interpreter and produces the false not_affected.

Suspect code

  • crates/socket-patch-core/src/crawlers/python_crawler.rs:896-899: find_pipenv_virtualenv_site_packages builds var from std::env::var only, so .env is never consulted. The same var feeds pipenv_workon_home_venvs (:1186-1193, PIPENV_CUSTOM_VENV_NAME), the WORKON_HOME lookup (:991), pipenv_venv_in_project (:434) and pipenv_uses_virtual_env (:420, PIPENV_IGNORE_VIRTUALENVS).
  • crates/socket-patch-core/src/utils/pipenv.rs:71-77 deliberately sets PIPENV_DONT_LOAD_ENV=1 for the version probe, treating .env as attacker-shaped input. If that's also the intent for discovery, it should be documented, and the fallback should refuse rather than patch the global interpreter (see Agent-mode scan in a Pipenv project without a Pipenv venv patches the system Python's site-packages in place instead of the project's venv/ (regression from #388) #504).

Related: #504 (a venv-less Pipenv project patches the system Python), #476 (the Poetry analogue: an out-of-tree env never probed, with a false VEX).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions