[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:276-284) takes VIRTUAL_ENV first and returns as soon as it has a site-packages. Pipenv only uses VIRTUAL_ENV when neither PIPENV_IGNORE_VIRTUALENVS nor PIPENV_ACTIVE is set (pipenv/environments.py: if "PIPENV_ACTIVE" not in os.environ and not self.PIPENV_IGNORE_VIRTUALENVS: self.PIPENV_VIRTUALENV = os.environ.get("VIRTUAL_ENV"), the same on 2018.11.26, 2023.12.1 and 2026.8.0). The crawler ignores both variables:
PIPENV_IGNORE_VIRTUALENVS=1 with some other venv activated. This is Pipenv's documented switch for keeping an activated venv from hijacking the project. Pipenv uses $WORKON_HOME/<name>-<hash>, but socket-patch patches the activated venv.
PIPENV_ACTIVE=1: you ran pipenv shell in project A, then cd into project B and ran socket-patch there. VIRTUAL_ENV still points at A's venv, and Pipenv in B deliberately ignores it. socket-patch patches project A's venv from project B.
Both cases report added / applied, status: success and exit 0. The venv that pipenv run / pipenv sync use in the project stays unpatched, and an unrelated venv gets modified. In hosted and vendored mode, the stale-install warning (vendor/pypi.rs:611, pipenv_stale_install_warning, which calls the same function) is judged against the wrong venv.
(socket-patch itself sets PIPENV_IGNORE_VIRTUALENVS=1 when it runs pipenv in utils/pipenv.rs:80, so the codebase already relies on this variable meaning "don't use VIRTUAL_ENV".)
This is related to #334 (stray venv/ or .venv shadowing Pipenv's venv), but it's a different trigger and needs a different fix. Pipenv does honour VIRTUAL_ENV by default, so moving Pipenv's placement ahead of ./.venv / ./venv for #334 won't fix it. The VIRTUAL_ENV probe itself has to respect the two Pipenv opt-outs when the project is a Pipenv project.
Impact
Agent mode reports success while the project's real venv keeps the vulnerable bytes, and it writes patched files into a venv the user didn't target (another project's venv, or a tool venv). CI that gates on the exit code passes. PIPENV_IGNORE_VIRTUALENVS=1 is common in shells and CI images that keep a tool venv activated. The pipenv shell → cd flow is everyday developer use.
Repro (Linux; mock patch API or a local manifest)
mkdir proj && cd proj
cat > Pipfile <<'EOF'
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
six = "==1.16.0"
EOF
PIPENV_IGNORE_VIRTUALENVS=1 pipenv install # -> ~/.local/share/virtualenvs/proj-XXXX
python3 -m venv /tmp/other && /tmp/other/bin/pip install six==1.16.0
export VIRTUAL_ENV=/tmp/other PIPENV_IGNORE_VIRTUALENVS=1 # or: PIPENV_ACTIVE=1 instead of IGNORE
pipenv --venv # ~/.local/share/virtualenvs/proj-XXXX
SOCKET_API_URL=http://127.0.0.1:18080 SOCKET_API_TOKEN=fake SOCKET_ORG_SLUG=test-org \
socket-patch scan --mode agent --json --yes; echo "exit=$?"
# status: success, patches: [{action: added}], exit=0
pipenv run python -c "import six; print(six.__file__, hasattr(six, 'SOCKET_PATCHED'))"
# ~/.local/share/virtualenvs/proj-XXXX/.../six.py False <- project venv unpatched
/tmp/other/bin/python -c "import six; print(hasattr(six, 'SOCKET_PATCHED'))"
# True <- unrelated venv patched
The same thing happens with socket-patch apply --offline against a local .socket/manifest.json (reproduced twice locally on Linux 2023.12.1 and 2026.8.0, and once more per cell in the probe).
Expected vs actual
- Expected: docs/testing/pipenv-compatibility.md ("Out-of-tree venv naming") says the crawler reproduces Pipenv's placement "so a bare
scan/rollback sees the project's venv; before, it fell through to the global interpreter and reported success while the venv stayed unpatched". The project's venv is the one pipenv --venv reports. CLI_CONTRACT.md: status: success / exit 0 means the requested patches are in place.
- Actual: it patches whichever venv
VIRTUAL_ENV names, even when Pipenv has been told to ignore it. It exits 0.
Control: with VIRTUAL_ENV set and neither opt-out set, Pipenv 2023.12.1 / 2026.8.0 also use the activated venv, so socket-patch is correct there (pass).
OS × version
| OS |
Pipenv |
IGNORE_VIRTUALENVS=1 |
PIPENV_ACTIVE=1 |
VIRTUAL_ENV only (control) |
no VIRTUAL_ENV (control) |
| Linux |
2023.12.1 |
fail: project venv UNPATCHED, other venv patched, exit 0 |
fail (same) |
pass (Pipenv uses it too) |
pass |
| Linux |
2026.8.0 |
fail |
fail |
pass |
pass |
| macOS |
2023.12.1 |
fail |
fail |
pass |
pass |
| macOS |
2026.8.0 |
fail |
fail |
pass |
pass |
| Windows |
2023.12.1 |
fail |
fail |
pass |
pass |
| Windows |
2026.8.0 |
fail |
fail |
pass |
pass |
| Linux |
2018.11.26 (py3.8) |
fail |
fail |
ambiguous (pipenv --venv names VIRTUAL_ENV, but pipenv run imports from the WORKON_HOME venv) |
pass |
First bad version
Not bisected. This isn't a regression: the unconditional VIRTUAL_ENV early return predates Pipenv venv discovery (added in 4.0.0), and neither PIPENV_IGNORE_VIRTUALENVS nor PIPENV_ACTIVE is read anywhere in the crawler.
Suspect code
crates/socket-patch-core/src/crawlers/python_crawler.rs:276-284: the unconditional VIRTUAL_ENV early return.
crates/socket-patch-core/src/vendor/pypi.rs:611: the stale-install warning goes through the same discovery.
Probe run: https://github.com/SocketDev/socket-patch/actions/runs/36779636041 (6 jobs: ubuntu / macos / windows × 2023.12.1 / 2026.8.0, scan --mode agent against a mock patch API). 2018.11.26 was checked locally on Linux.
[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:276-284) takesVIRTUAL_ENVfirst and returns as soon as it has a site-packages. Pipenv only usesVIRTUAL_ENVwhen neitherPIPENV_IGNORE_VIRTUALENVSnorPIPENV_ACTIVEis set (pipenv/environments.py:if "PIPENV_ACTIVE" not in os.environ and not self.PIPENV_IGNORE_VIRTUALENVS: self.PIPENV_VIRTUALENV = os.environ.get("VIRTUAL_ENV"), the same on 2018.11.26, 2023.12.1 and 2026.8.0). The crawler ignores both variables:PIPENV_IGNORE_VIRTUALENVS=1with some other venv activated. This is Pipenv's documented switch for keeping an activated venv from hijacking the project. Pipenv uses$WORKON_HOME/<name>-<hash>, but socket-patch patches the activated venv.PIPENV_ACTIVE=1: you ranpipenv shellin project A, thencdinto project B and ran socket-patch there.VIRTUAL_ENVstill points at A's venv, and Pipenv in B deliberately ignores it. socket-patch patches project A's venv from project B.Both cases report
added/applied,status: successand exit 0. The venv thatpipenv run/pipenv syncuse in the project stays unpatched, and an unrelated venv gets modified. In hosted and vendored mode, the stale-install warning (vendor/pypi.rs:611,pipenv_stale_install_warning, which calls the same function) is judged against the wrong venv.(socket-patch itself sets
PIPENV_IGNORE_VIRTUALENVS=1when it runspipenvinutils/pipenv.rs:80, so the codebase already relies on this variable meaning "don't use VIRTUAL_ENV".)This is related to #334 (stray
venv/or.venvshadowing Pipenv's venv), but it's a different trigger and needs a different fix. Pipenv does honourVIRTUAL_ENVby default, so moving Pipenv's placement ahead of./.venv/./venvfor #334 won't fix it. TheVIRTUAL_ENVprobe itself has to respect the two Pipenv opt-outs when the project is a Pipenv project.Impact
Agent mode reports success while the project's real venv keeps the vulnerable bytes, and it writes patched files into a venv the user didn't target (another project's venv, or a tool venv). CI that gates on the exit code passes.
PIPENV_IGNORE_VIRTUALENVS=1is common in shells and CI images that keep a tool venv activated. Thepipenv shell→cdflow is everyday developer use.Repro (Linux; mock patch API or a local manifest)
The same thing happens with
socket-patch apply --offlineagainst a local.socket/manifest.json(reproduced twice locally on Linux 2023.12.1 and 2026.8.0, and once more per cell in the probe).Expected vs actual
scan/rollbacksees the project's venv; before, it fell through to the global interpreter and reported success while the venv stayed unpatched". The project's venv is the onepipenv --venvreports. CLI_CONTRACT.md:status: success/ exit 0 means the requested patches are in place.VIRTUAL_ENVnames, even when Pipenv has been told to ignore it. It exits 0.Control: with
VIRTUAL_ENVset and neither opt-out set, Pipenv 2023.12.1 / 2026.8.0 also use the activated venv, so socket-patch is correct there (pass).OS × version
pipenv --venvnames VIRTUAL_ENV, butpipenv runimports from the WORKON_HOME venv)First bad version
Not bisected. This isn't a regression: the unconditional
VIRTUAL_ENVearly return predates Pipenv venv discovery (added in 4.0.0), and neitherPIPENV_IGNORE_VIRTUALENVSnorPIPENV_ACTIVEis read anywhere in the crawler.Suspect code
crates/socket-patch-core/src/crawlers/python_crawler.rs:276-284: the unconditionalVIRTUAL_ENVearly return.crates/socket-patch-core/src/vendor/pypi.rs:611: the stale-install warning goes through the same discovery.Probe run: https://github.com/SocketDev/socket-patch/actions/runs/36779636041 (6 jobs: ubuntu / macos / windows × 2023.12.1 / 2026.8.0,
scan --mode agentagainst a mock patch API). 2018.11.26 was checked locally on Linux.