[agent] Found by the scheduled Pipenv bug-hunt routine (ledger #313).
Summary
With [pipenv] use_pylock = true in the Pipfile, Pipenv 2026's pipenv lock writes both Pipfile.lock and pylock.toml. The pylock.toml carries [tool.pipenv] generated_from = "Pipfile.lock". When both files are present, Pipenv installs from Pipfile.lock: pipenv sync and pipenv install --deploy take the bytes from Pipfile.lock and ignore pylock.toml. I verified this below on 2026.0.0, 2026.4.0 and 2026.8.0.
scan --mode vendored routes this layout to the standalone python-lock flavor, because detect_pypi_flavor step 2 (a standalone pylock*.toml that contains the package) ranks above step 5 (Pipfile.lock → pipenv). So it rewrites only pylock.toml (archive = { path = ".socket/vendor/pypi/<uuid>/…whl" }) and leaves Pipfile.lock on PyPI. The run reports status: success, exit 0, "Vendored 1 package". Its only warning is pypi_multiple_lockfiles: "wiring pylock.toml; installs driven by Pipfile.lock retain their existing sources".
So every Pipenv install afterwards puts the unpatched upstream release in place. Hosted mode handles the same layout correctly: it pins both files, and pipenv sync gives the patched bytes.
This is distinct from #912. #912 is the pylock-only checkout, where Pipenv reads pylock.toml and drops archive. #912 already notes that with both files present "Pipenv installs from Pipfile.lock", which is exactly why vendoring the pylock alone misses here.
Impact
- The documented
use_pylock = true layout, vendored, never gets the patch in any Pipenv install (CI, Docker, --deploy), although the scan says it vendored the package.
- The user-visible remedy is awkward.
vendor --check (exit 1) says to delete whichever lock the project doesn't install from, but pipenv lock regenerates pylock.toml while use_pylock = true is set, and the next vendored run wires it again.
vex refuses, as it should (pkg:pypi/six@1.16.0 is wired … but Pipfile.lock resolves the same version from elsewhere, exit 1). So there's no false attestation. The defect is that the wrong file is chosen as the governing lock.
Repro (Linux; real Pipenv 2026.8.0, py3.11; local mock patch API serving a patched six 1.16.0 wheel)
mkdir app && cd app
cat > Pipfile <<'EOF'
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
six = "==1.16.0"
[requires]
python_version = "3.11"
[pipenv]
use_pylock = true
EOF
pipenv lock # writes Pipfile.lock AND pylock.toml
socket-patch scan --mode vendored --yes # exit 0, "Vendored 1 package."
# Warning: wiring pylock.toml; installs driven by Pipfile.lock retain their existing sources
grep -c socket/vendor Pipfile.lock # 0
grep -c socket/vendor pylock.toml # 1
pipenv --rm; pipenv install --deploy
pipenv run python -c "import six; print('SOCKET-PATCHED' in open(six.__file__).read())" # False
pipenv --rm; pipenv sync # same: False
socket-patch vendor --check # exit 1: wiring contested
socket-patch vex --offline --product pkg:pypi/app@1.0.0 -O vex.json # exit 1, refuses
# Control: the same project, hosted
socket-patch scan --mode hosted --yes # rewrittenFiles: [Pipfile.lock, pylock.toml]
pipenv --rm; pipenv sync # patched
# Which file Pipenv reads: hosted Pipfile.lock + pristine pylock.toml -> sync / --deploy give the PATCHED bytes
scan --mode vendored --dry-run previews the same pylock-only wiring. Reproduced 2/2 on 2026.8.0, plus once each on 2026.0.0 and 2026.4.0.
Expected vs actual
- Expected: vendored wires the file the project's installer reads. When a
Pipfile sits beside both locks (and pylock.toml says generated_from = "Pipfile.lock"), that's Pipfile.lock. The flavor router's own doc says it routes by "this tool manages installs". docs/ecosystems.md lists Pipenv Pipfile.lock vendoring as supported ("every Pipfile.lock category is rewired"). It would also be reasonable to wire both files, as hosted mode does. CLI_CONTRACT.md: "A dep counts as redirected only when its hosted-artifact URL … actually landed in a project file". The vendored analogue is a rewrite the installer honours.
- Actual: only
pylock.toml is wired, and Pipenv ignores it while Pipfile.lock exists. The scan succeeds, and every Pipenv install is unpatched.
OS × version
| Pipenv |
both locks written by pipenv lock |
vendored wires |
pipenv sync / --deploy after vendored |
control: hosted Pipfile.lock + pristine pylock.toml |
| 2026.0.0 |
yes |
pylock.toml only |
UPSTREAM |
PATCHED |
| 2026.4.0 |
yes |
pylock.toml only |
UPSTREAM |
PATCHED |
| 2026.8.0 |
yes |
pylock.toml only (2/2) |
UPSTREAM (sync and --deploy) |
PATCHED |
| ≤ 2025.x |
n/a (no pylock support) |
|
|
|
| macOS / Windows |
not probed (the routing is pure path-presence logic) |
|
|
|
First bad version
Not bisected. v4.0.0 has no pylock vendoring, so this is unreleased v5 behaviour on main b96a785. #1044 moved the tool-lock order into formats/governing_locks.rs (PYPI_TOOL_LOCKS), but the standalone-lock branch that wins here sits before that table and predates it.
Suspect code
crates/socket-patch-core/src/vendor/pypi.rs:251 (doc, step 2) and :310–:324: if !has_uv_lock && matching_additional_lock { … return Ok((PypiFlavor::PythonLocks, warnings)) }. Any standalone pylock that contains the package beats Pipfile.lock, and nothing checks for a Pipfile or a [tool.pipenv] table in the pylock.
crates/socket-patch-core/src/formats/governing_locks.rs:115: PYPI_TOOL_LOCKS doesn't place Pipenv-generated pylocks relative to Pipfile.lock.
Related: #912 (pylock-only Pipenv checkout; Pipenv drops archive), #612 (pypi_multiple_lockfiles for a sibling requirements.txt), #1114 (inventory vs router disagreement on empty poetry / pdm locks).
[agent] Found by the scheduled Pipenv bug-hunt routine (ledger #313).
Summary
With
[pipenv] use_pylock = truein the Pipfile, Pipenv 2026'spipenv lockwrites bothPipfile.lockandpylock.toml. The pylock.toml carries[tool.pipenv] generated_from = "Pipfile.lock". When both files are present, Pipenv installs fromPipfile.lock:pipenv syncandpipenv install --deploytake the bytes fromPipfile.lockand ignorepylock.toml. I verified this below on 2026.0.0, 2026.4.0 and 2026.8.0.scan --mode vendoredroutes this layout to the standalone python-lock flavor, becausedetect_pypi_flavorstep 2 (a standalonepylock*.tomlthat contains the package) ranks above step 5 (Pipfile.lock→ pipenv). So it rewrites onlypylock.toml(archive = { path = ".socket/vendor/pypi/<uuid>/…whl" }) and leavesPipfile.lockon PyPI. The run reportsstatus: success, exit 0, "Vendored 1 package". Its only warning ispypi_multiple_lockfiles: "wiring pylock.toml; installs driven by Pipfile.lock retain their existing sources".So every Pipenv install afterwards puts the unpatched upstream release in place. Hosted mode handles the same layout correctly: it pins both files, and
pipenv syncgives the patched bytes.This is distinct from #912. #912 is the pylock-only checkout, where Pipenv reads
pylock.tomland dropsarchive. #912 already notes that with both files present "Pipenv installs fromPipfile.lock", which is exactly why vendoring the pylock alone misses here.Impact
use_pylock = truelayout, vendored, never gets the patch in any Pipenv install (CI, Docker,--deploy), although the scan says it vendored the package.vendor --check(exit 1) says to delete whichever lock the project doesn't install from, butpipenv lockregeneratespylock.tomlwhileuse_pylock = trueis set, and the next vendored run wires it again.vexrefuses, as it should (pkg:pypi/six@1.16.0 is wired … but Pipfile.lock resolves the same version from elsewhere, exit 1). So there's no false attestation. The defect is that the wrong file is chosen as the governing lock.Repro (Linux; real Pipenv 2026.8.0, py3.11; local mock patch API serving a patched
six 1.16.0wheel)scan --mode vendored --dry-runpreviews the same pylock-only wiring. Reproduced 2/2 on 2026.8.0, plus once each on 2026.0.0 and 2026.4.0.Expected vs actual
Pipfilesits beside both locks (and pylock.toml saysgenerated_from = "Pipfile.lock"), that'sPipfile.lock. The flavor router's own doc says it routes by "this tool manages installs". docs/ecosystems.md lists PipenvPipfile.lockvendoring as supported ("everyPipfile.lockcategory is rewired"). It would also be reasonable to wire both files, as hosted mode does. CLI_CONTRACT.md: "A dep counts as redirected only when its hosted-artifact URL … actually landed in a project file". The vendored analogue is a rewrite the installer honours.pylock.tomlis wired, and Pipenv ignores it whilePipfile.lockexists. The scan succeeds, and every Pipenv install is unpatched.OS × version
pipenv lockpipenv sync/--deployafter vendoredPipfile.lock+ pristinepylock.toml--deploy)First bad version
Not bisected. v4.0.0 has no pylock vendoring, so this is unreleased v5 behaviour on main
b96a785. #1044 moved the tool-lock order intoformats/governing_locks.rs(PYPI_TOOL_LOCKS), but the standalone-lock branch that wins here sits before that table and predates it.Suspect code
crates/socket-patch-core/src/vendor/pypi.rs:251(doc, step 2) and:310–:324:if !has_uv_lock && matching_additional_lock { … return Ok((PypiFlavor::PythonLocks, warnings)) }. Any standalone pylock that contains the package beatsPipfile.lock, and nothing checks for aPipfileor a[tool.pipenv]table in the pylock.crates/socket-patch-core/src/formats/governing_locks.rs:115:PYPI_TOOL_LOCKSdoesn't place Pipenv-generated pylocks relative toPipfile.lock.Related: #912 (pylock-only Pipenv checkout; Pipenv drops
archive), #612 (pypi_multiple_lockfilesfor a sibling requirements.txt), #1114 (inventory vs router disagreement on empty poetry / pdm locks).