[agent] Found by the scheduled Pipenv bug-hunt routine (ledger #313).
Summary
find_local_venv_site_packages (crates/socket-patch-core/src/crawlers/python_crawler.rs:265-311) probes VIRTUAL_ENV, then ./.venv and ./venv. Only when none of them has a site-packages does it look for Pipenv's out-of-tree venv (if results.is_empty() at :308). Pipenv doesn't work that way:
- Pipenv never uses a
venv/ directory. A leftover python -m venv venv in the project (very common) makes socket-patch patch the wrong interpreter's site-packages, and the one Pipenv actually uses is never looked at.
- With
PIPENV_VENV_IN_PROJECT=0 (Pipenv 2023+ treat 0 as an explicit "no"), Pipenv ignores an existing ./.venv directory and uses $WORKON_HOME/<name>-<hash>. socket-patch still picks ./.venv.
In both cases the patched package is reported skipped / package_not_installed, the envelope says status: success, exit is 0, and pipenv run python still imports the unpatched file. In hosted mode the same misdiscovery also suppresses the redirect_pypi_stale_install warning: the scan redirects and says nothing, while Pipenv's venv keeps the upstream bytes.
This is the Pipenv counterpart of #327 (Poetry, "in-project = false with a stray .venv"). The code path is separate (find_pipenv_virtualenv_site_packages).
Impact
A user runs socket-patch scan in a normal Pipenv project and gets success with zero patches applied. Their real venv stays vulnerable with no error. CI that gates on the exit code passes.
Repro (Linux shown; macOS and Windows identical, see table)
mkdir proj && cd proj
cat > Pipfile <<'EOF'
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
six = "==1.16.0"
EOF
python3.12 -m venv venv # stray, unrelated venv
pipenv install --python python3.12 # Pipenv uses ~/.local/share/virtualenvs/proj-XXXX
export SOCKET_API_URL=http://127.0.0.1:18080 SOCKET_API_TOKEN=fake SOCKET_ORG_SLUG=test-org # mock patch API
socket-patch scan --mode agent --json --yes; echo "exit=$?"
# status: success, patches: [{action: skipped, errorCode: package_not_installed}], exit=0
pipenv run python -c "import six; print(six.__file__, hasattr(six, 'SOCKET_PATCHED'))"
# ~/.local/share/virtualenvs/proj-XXXX/lib/python3.12/site-packages/six.py False
# Variant 2 (Pipenv 2023+): replace the stray venv with a .venv and opt out of in-project
rm -rf venv && python3.12 -m venv .venv && export PIPENV_VENV_IN_PROJECT=0
Without the stray directory, the same project patches correctly (added, six PATCHED), so the out-of-tree discovery itself works.
Expected vs actual
- Expected: docs/testing/pipenv-compatibility.md says the agent mode patches "the project's venv — in-project
.venv, VIRTUAL_ENV, or Pipenv's default $WORKON_HOME/<dir>-<hash>[-<python>]", and that the discovery exists so a bare scan "sees the project's venv" instead of "report[ing] success while the venv stayed unpatched". For a Pipenv project (a Pipfile in cwd), the venv Pipenv actually resolves should win: .venv only when Pipenv would use it (PIPENV_VENV_IN_PROJECT unset/truthy), and never venv/. At the very least, the scan shouldn't report success with the patch unapplied.
- Actual:
venv/, or a .venv that Pipenv ignores, shadows Pipenv's venv; package_not_installed, exit 0.
OS × version (probe run below, plus local Linux)
| Case |
Linux 2018.11.26 |
Linux 2023.12.1 |
Linux 2026.8.0 |
macOS 2023.12.1 |
macOS 2026.8.0 |
Windows 2023.12.1 |
Windows 2026.8.0 |
| baseline (no stray dir) |
✅ |
✅ |
✅ |
✅ |
✅ |
✅ |
✅ |
stray venv/ |
❌ |
❌ |
❌ |
❌ |
❌ |
❌ |
❌ |
.venv + PIPENV_VENV_IN_PROJECT=0 |
✅ (n/a¹) |
❌ |
❌ |
❌ |
❌ |
❌ |
❌ |
¹ Pipenv 2018 and 2022 read PIPENV_VENV_IN_PROJECT=0 as truthy and really do use ./.venv, so socket-patch's choice happens to be right there.
Hosted mode, stray venv/, Linux 2026.8.0: redirected: 1 with no redirect_pypi_stale_install, although Pipenv's venv holds the upstream bytes (the warning fires correctly without the stray dir).
Each failing cell was reproduced at least twice (local runs across versions, and the probe).
First bad: not a regression. Release 3.3.0 didn't discover Pipenv's out-of-tree venv at all (the baseline is also unpatched there). The gap has been present since out-of-tree discovery landed.
Suspect code
crates/socket-patch-core/src/crawlers/python_crawler.rs:286-290: .venv / venv are probed unconditionally, before the Pipenv step.
crates/socket-patch-core/src/crawlers/python_crawler.rs:308: if results.is_empty() gates Pipenv discovery on nothing else being found.
find_pipenv_virtualenv_site_packages_with (:647) doesn't consult PIPENV_VENV_IN_PROJECT.
Probe run: https://github.com/SocketDev/socket-patch/actions/runs/36739663342
[agent] Found by the scheduled Pipenv bug-hunt routine (ledger #313).
Summary
find_local_venv_site_packages(crates/socket-patch-core/src/crawlers/python_crawler.rs:265-311) probesVIRTUAL_ENV, then./.venvand./venv. Only when none of them has a site-packages does it look for Pipenv's out-of-tree venv (if results.is_empty()at:308). Pipenv doesn't work that way:venv/directory. A leftoverpython -m venv venvin the project (very common) makes socket-patch patch the wrong interpreter's site-packages, and the one Pipenv actually uses is never looked at.PIPENV_VENV_IN_PROJECT=0(Pipenv 2023+ treat0as an explicit "no"), Pipenv ignores an existing./.venvdirectory and uses$WORKON_HOME/<name>-<hash>. socket-patch still picks./.venv.In both cases the patched package is reported
skipped/package_not_installed, the envelope saysstatus: success, exit is 0, andpipenv run pythonstill imports the unpatched file. In hosted mode the same misdiscovery also suppresses theredirect_pypi_stale_installwarning: the scan redirects and says nothing, while Pipenv's venv keeps the upstream bytes.This is the Pipenv counterpart of #327 (Poetry, "in-project = false with a stray .venv"). The code path is separate (
find_pipenv_virtualenv_site_packages).Impact
A user runs
socket-patch scanin a normal Pipenv project and gets success with zero patches applied. Their real venv stays vulnerable with no error. CI that gates on the exit code passes.Repro (Linux shown; macOS and Windows identical, see table)
Without the stray directory, the same project patches correctly (
added, six PATCHED), so the out-of-tree discovery itself works.Expected vs actual
.venv,VIRTUAL_ENV, or Pipenv's default$WORKON_HOME/<dir>-<hash>[-<python>]", and that the discovery exists so a barescan"sees the project's venv" instead of "report[ing] success while the venv stayed unpatched". For a Pipenv project (aPipfilein cwd), the venv Pipenv actually resolves should win:.venvonly when Pipenv would use it (PIPENV_VENV_IN_PROJECTunset/truthy), and nevervenv/. At the very least, the scan shouldn't report success with the patch unapplied.venv/, or a.venvthat Pipenv ignores, shadows Pipenv's venv;package_not_installed, exit 0.OS × version (probe run below, plus local Linux)
venv/.venv+PIPENV_VENV_IN_PROJECT=0¹ Pipenv 2018 and 2022 read
PIPENV_VENV_IN_PROJECT=0as truthy and really do use./.venv, so socket-patch's choice happens to be right there.Hosted mode, stray
venv/, Linux 2026.8.0:redirected: 1with noredirect_pypi_stale_install, although Pipenv's venv holds the upstream bytes (the warning fires correctly without the stray dir).Each failing cell was reproduced at least twice (local runs across versions, and the probe).
First bad: not a regression. Release 3.3.0 didn't discover Pipenv's out-of-tree venv at all (the baseline is also unpatched there). The gap has been present since out-of-tree discovery landed.
Suspect code
crates/socket-patch-core/src/crawlers/python_crawler.rs:286-290:.venv/venvare probed unconditionally, before the Pipenv step.crates/socket-patch-core/src/crawlers/python_crawler.rs:308:if results.is_empty()gates Pipenv discovery on nothing else being found.find_pipenv_virtualenv_site_packages_with(:647) doesn't consultPIPENV_VENV_IN_PROJECT.Probe run: https://github.com/SocketDev/socket-patch/actions/runs/36739663342