Skip to content

Agent-mode scan skips Pipenv's out-of-tree venv when the project has a stray venv/ directory or a .venv with PIPENV_VENV_IN_PROJECT=0, and still exits 0 #334

Description

[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

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions