Skip to content

Agent mode patches only the WORKON_HOME venv when a Pipenv project also has an auto-detected ./.venv, so Pipenv 2018–2026.1 keep running the unpatched .venv and VEX attests not_affected (regression from #388) #529

Description

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

Summary

Take a Pipenv project that has both a ./.venv directory and an existing $WORKON_HOME/<name>-<hash> venv, with neither PIPENV_VENV_IN_PROJECT nor [pipenv] venv_in_project set. #388 (ccd43f58) deliberately returns both site-packages for this case, WORKON_HOME first (crates/socket-patch-core/src/crawlers/python_crawler.rs:486-488). Its doc comment (python_crawler.rs:463-467) and docs/testing/pipenv-compatibility.md both promise that "both are patched, since Pipenv 2026.2+ uses the WORKON_HOME venv and older releases use .venv".

That isn't what happens. crawl_all dedupes packages by purl, so the first site-packages wins (python_crawler.rs:1742, if !seen.insert(purl.clone()) { continue; }). Only the WORKON_HOME copy gets patched. Every Pipenv release before 2026.2 (2018.x through 2025.x) resolves this shape to ./.venv, so the interpreter pipenv run uses stays vulnerable:

  • scan --mode agent reports applied: 1 and exits 0.
  • A follow-up apply says All files already match afterHash (skipped) and exits 0.
  • vex attests not_affected / inline_mitigations_already_exist.

Before #388 (ccd43f58~1), the same projects had ./.venv patched, which was correct for 2018–2025 and wrong for 2026.2+ (that was #334). The fix flipped which half of the version range is broken instead of covering both.

Impact

Silent false-success on the most widely deployed Pipenv versions. A .venv alongside a leftover WORKON_HOME venv is a common shape: an IDE or python -m venv .venv that Pipenv then auto-adopts, or a project that moved to in-project venvs while an old out-of-tree venv stayed behind. Patches land in a venv Pipenv no longer uses, and the VEX document says the live one is fixed. Nothing goes to stderr.

Hosted mode isn't affected: the redirect_pypi_stale_install warning fires, and hosted vex omits the patch ("the patched files still hold the original content").

Repro (Linux, main 61cfb9b, real Pipenv)

Any agent patch for six@1.16.0 works. I used a local mock of the patch API (batch / by-package / package / view, the same shapes as crates/socket-patch-cli/tests/vex_pipenv_pip_real/mod.rs).

SP=/path/to/target/release/socket-patch
API="--api-url $MOCK --api-token fake --org test-org --patch-server-url $MOCK"
export WORKON_HOME=$PWD/workon PIPENV_IGNORE_VIRTUALENVS=1; unset PIPENV_VENV_IN_PROJECT
mkdir proj && cd proj
pipenv --python python3.11 install six==1.16.0                         # -> $WORKON_HOME/proj-XXXXXXXX
PIPENV_VENV_IN_PROJECT=1 pipenv --python python3.11 install --deploy   # -> ./.venv (same lock)
pipenv --venv                                   # 2018.11.26 / 2022.12.19 / 2023.12.1: …/proj/.venv
$SP scan --mode agent --yes --json $API         # status success, applied 1, exit 0
pipenv run python -c 'import six; print(six.__file__)'   # …/proj/.venv/…/six.py -> UNPATCHED
$WORKON_HOME/proj-*/bin/python -c 'import six'           # PATCHED (not the venv Pipenv uses)
$SP apply --json $API                           # skipped: "All files already match afterHash", exit 0
$SP vex --product pkg:pypi/app@0.1.0 --output vex.json $API   # not_affected / inline_mitigations_already_exist

Expected vs actual

  • Expected (docs/testing/pipenv-compatibility.md, agent column: "With an auto-detected .venv and an existing WORKON_HOME venv, both are patched"; the same promise is in the pipenv_project_site_packages doc comment): both ./.venv and the WORKON_HOME venv get patched, or at least the one the installed Pipenv resolves. vex mustn't attest not_affected while the venv Pipenv runs is unpatched.
  • Actual: only the first candidate (WORKON_HOME) is patched. ./.venv keeps the upstream bytes, the command exits 0, and VEX attests not_affected.

OS × version (Linux; each cell run twice on main, from fresh directories)

Pipenv (Python) pipenv --venv main 61cfb9b pre-#388 ccd43f58~1
2018.11.26 (py3.8) ./.venv fail: .venv unpatched, WORKON patched, VEX not_affected (2/2) pass (.venv patched)
2022.12.19 (py3.10) ./.venv fail (2/2) not run
2023.12.1 (py3.11) ./.venv fail (2/2) pass (.venv patched)
2026.8.0 (py3.12) WORKON_HOME pass (2/2) fail (#334)

I didn't probe macOS or Windows: probe branches are blocked in this sandbox because branch deletion through the git proxy fails (see ledger #313). The logic is OS-independent.

First bad commit: ccd43f58 (#388). The latest release, v4.0.0, predates #388. I tested the parent commit ccd43f58~1, not the v4.0.0 binary itself.

Suspect code

  • crates/socket-patch-core/src/crawlers/python_crawler.rs:486-488: returns [WORKON_HOME…, .venv…] for the "nothing explicit" case.
  • crates/socket-patch-core/src/crawlers/python_crawler.rs:1742: the purl dedup in crawl_all keeps only the first site-packages per purl, so the "both" promise collapses to "WORKON_HOME only".

Related, but a different PM and a different discovery path with the same first-wins collapse: #526 (Poetry, several -py3.x envs). Also related: #334 (the 2026.2+ half of this shape, now fixed).

Side note, same mechanism, not verified end to end: with PIPENV_PYTHON=python3.11 set, pipenv --venv (2026.8.0) reports $WORKON_HOME/proj-<hash>-python3.11. If an unsuffixed proj-<hash> venv also exists, agent mode patches the unsuffixed one, because it sorts first. I couldn't confirm what pipenv run imports in that shape, so I'm not claiming it here.

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