[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.
[agent] Found by the scheduled Pipenv bug-hunt routine (ledger #313).
Summary
Take a Pipenv project that has both a
./.venvdirectory and an existing$WORKON_HOME/<name>-<hash>venv, with neitherPIPENV_VENV_IN_PROJECTnor[pipenv] venv_in_projectset. #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) anddocs/testing/pipenv-compatibility.mdboth 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_alldedupes 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 interpreterpipenv runuses stays vulnerable:scan --mode agentreportsapplied: 1and exits 0.applysaysAll files already match afterHash(skipped) and exits 0.vexattestsnot_affected/inline_mitigations_already_exist.Before #388 (
ccd43f58~1), the same projects had./.venvpatched, 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
.venvalongside a leftover WORKON_HOME venv is a common shape: an IDE orpython -m venv .venvthat 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_installwarning fires, and hostedvexomits the patch ("the patched files still hold the original content").Repro (Linux, main
61cfb9b, real Pipenv)Any agent patch for
six@1.16.0works. I used a local mock of the patch API (batch/by-package/package/view, the same shapes ascrates/socket-patch-cli/tests/vex_pipenv_pip_real/mod.rs).Expected vs actual
docs/testing/pipenv-compatibility.md, agent column: "With an auto-detected.venvand an existing WORKON_HOME venv, both are patched"; the same promise is in thepipenv_project_site_packagesdoc comment): both./.venvand the WORKON_HOME venv get patched, or at least the one the installed Pipenv resolves.vexmustn't attestnot_affectedwhile the venv Pipenv runs is unpatched../.venvkeeps the upstream bytes, the command exits 0, and VEX attestsnot_affected.OS × version (Linux; each cell run twice on main, from fresh directories)
pipenv --venv61cfb9bccd43f58~1./.venv.venvunpatched, WORKON patched, VEXnot_affected(2/2).venvpatched)./.venv./.venv.venvpatched)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 commitccd43f58~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 incrawl_allkeeps 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.xenvs). 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.11set,pipenv --venv(2026.8.0) reports$WORKON_HOME/proj-<hash>-python3.11. If an unsuffixedproj-<hash>venv also exists, agent mode patches the unsuffixed one, because it sorts first. I couldn't confirm whatpipenv runimports in that shape, so I'm not claiming it here.