[agent] Found by the scheduled Pipenv bug-hunt routine (ledger #313).
Summary
patch/redirect/pipenv.rs handles a conflicting Pipfile.lock entry (another version pinned, a foreign source, a VCS/path/file dependency) in one of two ways. It decides which by checking files.contains_key("Pipfile"):
- Live lock (a Pipfile beside it): refuse the patch and veto the sibling pypi rewriters, "so nothing is half-redirected".
- Abandoned lock (no Pipfile): refuse only this file, and still let
requirements.txt / uv.lock / … be redirected.
But the disk hosted flow builds files from REDIRECT_CANDIDATE_FILES (crates/socket-patch-cli/src/commands/scan/hosted.rs:40), and that list contains Pipfile.lock but not Pipfile. So files.contains_key("Pipfile") is always false. Every live Pipenv project takes the "abandoned lock" branch, and:
- the conflict veto never fires, so a sibling
requirements.txt (for example one exported with pipenv requirements > requirements.txt for Docker or Heroku) is redirected;
- the scan reports
redirected: 1, status: success, exit 0;
- the warning says
(no Pipfile beside the lock: the sibling Python files are still redirected), which is false.
The unit tests in pipenv.rs pass a Pipfile key directly (pipenv.rs:558, :866), so they don't exercise the real file set.
Impact
Pipenv installs from Pipfile.lock, which still points at the user's own (unpatched) wheel. The scan still reports the patch as redirected. pipenv sync into a clean venv installs unpatched six (verified). vex does correctly refuse (no_applicable_patches), so there's no false attestation. But the scan's success and redirected count are wrong. The half-redirect is exactly the state that the comment at pipenv.rs:280-293 says the veto exists to prevent.
The same missing key also produces the misleading message in the vendored → hosted case reported on #328.
Repro (Linux; mock patch API as in the Poetry/uv bughunt probes, serving six 1.16.0)
mkdir c1 && cd c1 && mkdir wheels
cp six-1.16.0-py2.py3-none-any.whl wheels/ # the pristine PyPI wheel
cat > Pipfile <<'EOF'
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
six = {file = "wheels/six-1.16.0-py2.py3-none-any.whl"}
EOF
PIPENV_VENV_IN_PROJECT=1 pipenv install
echo 'six==1.16.0' > requirements.txt
git init -q
export SOCKET_API_URL=http://127.0.0.1:18080 SOCKET_API_TOKEN=fake SOCKET_ORG_SLUG=test-org
socket-patch scan --mode hosted --json --yes; echo "exit=$?"
# status: success, redirected: 1, rewrittenFiles: ["requirements.txt"]
# warning redirect_pipenv_refused: "Pipenv source for six already exists
# (no Pipfile beside the lock: the sibling Python files are still redirected)"
cat requirements.txt
# six @ http://127.0.0.1:18080/patch/pypi/six/1.16.0/.../six-1.16.0-py2.py3-none-any.whl --hash=sha256:096e50…
rm -rf .venv && pipenv sync
pipenv run python -c "import six; print(hasattr(six, 'SOCKET_PATCHED'))" # False
Reproduced twice on the current main (scan → rollback → scan).
Expected vs actual
- Expected: per the rewriter's own contract (
pipenv.rs:280-293), a conflict in a live Pipfile.lock means Pipenv won't pick the patch up, so the patch is refused project-wide. That means redirected: 0, requirements.txt untouched, and a redirect_pipenv_refused warning without the "no Pipfile" clause. docs/testing/pipenv-compatibility.md also describes the CLI as reading the project's Pipfile.
- Actual: the patch is half-redirected into
requirements.txt, the scan reports success with redirected: 1, and the warning claims there is no Pipfile.
OS × version
| Pipenv (Linux) |
half-redirect of requirements.txt |
| 2018.11.26 (py3.8) |
❌ reproduces |
| 2022.12.19 (py3.8) |
❌ reproduces |
| 2026.8.0 (py3.12) |
❌ reproduces (twice) |
The macOS and Windows cells are running in the probe below. The code path is pure file-set logic with no OS-specific branches.
First bad: 07a6b88 (#242, "Support hosted Pipenv patches and safe vendoring"), which introduced the guard. It first shipped in 4.0.0. Release 3.3.0 had no hosted mode.
Suspect code
crates/socket-patch-cli/src/commands/scan/hosted.rs:40: REDIRECT_CANDIDATE_FILES has no "Pipfile".
crates/socket-patch-core/src/patch/redirect/pipenv.rs:295: Err(PlanError::Conflict(detail)) if files.contains_key("Pipfile") is unreachable in the disk flow.
- It may also be worth checking whether the in-memory engine's file set (
crates/socket-patch-cli/src/hosted_memory/) carries Pipfile.
Probe run (macOS/Windows): https://github.com/SocketDev/socket-patch/actions/runs/36739663342
[agent] Found by the scheduled Pipenv bug-hunt routine (ledger #313).
Summary
patch/redirect/pipenv.rshandles a conflictingPipfile.lockentry (another version pinned, a foreign source, a VCS/path/file dependency) in one of two ways. It decides which by checkingfiles.contains_key("Pipfile"):requirements.txt/uv.lock/ … be redirected.But the disk hosted flow builds
filesfromREDIRECT_CANDIDATE_FILES(crates/socket-patch-cli/src/commands/scan/hosted.rs:40), and that list containsPipfile.lockbut notPipfile. Sofiles.contains_key("Pipfile")is always false. Every live Pipenv project takes the "abandoned lock" branch, and:requirements.txt(for example one exported withpipenv requirements > requirements.txtfor Docker or Heroku) is redirected;redirected: 1,status: success, exit 0;(no Pipfile beside the lock: the sibling Python files are still redirected), which is false.The unit tests in
pipenv.rspass aPipfilekey directly (pipenv.rs:558,:866), so they don't exercise the real file set.Impact
Pipenv installs from
Pipfile.lock, which still points at the user's own (unpatched) wheel. The scan still reports the patch as redirected.pipenv syncinto a clean venv installs unpatchedsix(verified).vexdoes correctly refuse (no_applicable_patches), so there's no false attestation. But the scan's success andredirectedcount are wrong. The half-redirect is exactly the state that the comment atpipenv.rs:280-293says the veto exists to prevent.The same missing key also produces the misleading message in the vendored → hosted case reported on #328.
Repro (Linux; mock patch API as in the Poetry/uv bughunt probes, serving
six 1.16.0)Reproduced twice on the current main (scan → rollback → scan).
Expected vs actual
pipenv.rs:280-293), a conflict in a livePipfile.lockmeans Pipenv won't pick the patch up, so the patch is refused project-wide. That meansredirected: 0,requirements.txtuntouched, and aredirect_pipenv_refusedwarning without the "no Pipfile" clause. docs/testing/pipenv-compatibility.md also describes the CLI as reading the project's Pipfile.requirements.txt, the scan reports success withredirected: 1, and the warning claims there is no Pipfile.OS × version
The macOS and Windows cells are running in the probe below. The code path is pure file-set logic with no OS-specific branches.
First bad:
07a6b88(#242, "Support hosted Pipenv patches and safe vendoring"), which introduced the guard. It first shipped in 4.0.0. Release 3.3.0 had no hosted mode.Suspect code
crates/socket-patch-cli/src/commands/scan/hosted.rs:40:REDIRECT_CANDIDATE_FILEShas no"Pipfile".crates/socket-patch-core/src/patch/redirect/pipenv.rs:295:Err(PlanError::Conflict(detail)) if files.contains_key("Pipfile")is unreachable in the disk flow.crates/socket-patch-cli/src/hosted_memory/) carriesPipfile.Probe run (macOS/Windows): https://github.com/SocketDev/socket-patch/actions/runs/36739663342