Skip to content

Agent-mode scan patches the activated VIRTUAL_ENV even when PIPENV_IGNORE_VIRTUALENVS or PIPENV_ACTIVE tells Pipenv to ignore it, leaving the Pipenv venv unpatched with exit 0 #384

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: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.

Activity

  1. mikolalysenko commented on Sep 30, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triaged: priority:p1 (Pipenv / PyPI family). Not a duplicate, and no open or merged PR fixes it (the VIRTUAL_ENV probe in find_local_venv_site_packages on main still ignores PIPENV_IGNORE_VIRTUALENVS and PIPENV_ACTIVE).

    Shares root cause with #334: for a Pipenv project, find_local_venv_site_packages (crawlers/python_crawler.rs) runs a generic probe order (VIRTUAL_ENV, ./.venv, ./venv, and only then the $WORKON_HOME venv) instead of resolving the venv the way Pipenv does. Will be fixed together. One Pipenv-specific resolver that follows Pipenv's own order covers both issues: honour VIRTUAL_ENV only without PIPENV_ACTIVE / PIPENV_IGNORE_VIRTUALENVS, honour PIPENV_VENV_IN_PROJECT and [pipenv] venv_in_project, and never use venv/.


    Generated by Claude Code

  2. mikolalysenko commented on Sep 30, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Claiming this issue (with #334; shared root cause: Pipenv venv discovery uses a generic probe order instead of Pipenv's own resolution). Branch: agent/fix-pipenv-venv-resolution. Claim-ID: 2026-09-30T22:21:16Z-1947b5


    Generated by Claude Code

  3. mikolalysenko commented on Sep 30, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Draft fix PR: #388


    Generated by Claude Code

  4. mikolalysenko commented on Oct 1, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] On main 2463257 (#277, v5) the same discovery bug also causes a false VEX attestation in hosted mode.

    With VIRTUAL_ENV pointing at an unrelated venv (one without six) and PIPENV_IGNORE_VIRTUALENVS=1, Pipenv uses its WORKON_HOME venv. socket-patch looks only at VIRTUAL_ENV, so:

    • scan --mode hosted gives redirected: 1 and no redirect_pypi_stale_install, although Pipenv's venv still holds upstream six. Pipenv won't reinstall a warm venv.
    • socket-patch vex --product pkg:pypi/x@1 emits not_affected / inline_mitigations_already_exist ("Patched via Socket patch … (redirected)"), while pipenv run python -c "import six; print(hasattr(six,'SOCKET_PATCHED'))" prints False.
    • Control (same project, VIRTUAL_ENV unset): the stale warning fires, and vex omits the patch as not_applied.
    Pipenv (Linux, py3.12) control VIRTUAL_ENV=other + PIPENV_IGNORE_VIRTUALENVS=1
    2023.12.1 ✅ ❌ no warning, not_affected
    2026.8.0 ✅ ❌ no warning, not_affected

    I ran each twice. The repro script is the same as the one I just posted on #334 (same root cause, crawlers/python_crawler.rs venv selection).


    Generated by Claude Code

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