Repository navigation
Hosted and vendored scans rewrite a Pipenv project's pylock.toml to an archive entry that Pipenv 2026.4+ ignores, so pipenv sync silently installs the unpatched release from PyPI #912
Description
[agent] Found by the scheduled Pipenv bug-hunt routine (ledger #313).
Summary
Pipenv 2026 supports PEP 751 lock files. With [pipenv] use_pylock = true in the Pipfile, pipenv lock writes pylock.toml next to Pipfile.lock. When Pipfile.lock is absent (a pylock-only checkout, or a pylock.toml from another tool next to a Pipfile), pipenv sync installs from pylock.toml (Lockfile.content / get_or_create_lockfile's pylock branch).
scan --mode hosted and scan --mode vendored rewrite that pylock.toml the PEP 751 way: they replace index and wheels with archive = { url = "<hosted wheel>" … } (hosted) or archive = { path = ".socket/vendor/pypi/<uuid>/<wheel>" … } (vendored), and keep version = "1.16.0". Pipenv reads pylock.toml through pipenv/utils/pylock.py PylockFile.convert_to_pipenv_lockfile, which copies only version, marker and the wheels / sdist hashes. It ignores archive. So the patched entry becomes six = {version = "==1.16.0"} with no hashes and no URL, and pipenv sync installs the upstream release from PyPI, exit 0.
The scan reports status: success, redirected: 1, rewrittenFiles: ["pylock.toml"] and gives no warning.
Impact
- A Pipenv 2026.4+ project that installs from
pylock.tomlthinks it is patched, but every freshpipenv sync(CI, Docker) installs the vulnerable bytes without a hash check. - Vendored:
socket-patch vexthen attestsnot_affected/inline_mitigations_already_existfor the package, even though the venvpipenv syncjust built holds the upstreamsix.py. That is a false VEX statement. In hosted mode, vex refuses (no_applicable_patches), which is correct. - When
Pipfile.lockis also present (the normaluse_pylock = truelayout), Pipenv installs fromPipfile.lockand the patch lands (pass, see the table below). Only the pylock-only shape is affected.
Repro (Linux, real Pipenv 2026.8.0, local mock patch API serving a patched six 1.16.0 wheel)
mkdir demo && cd demo
cat > Pipfile <<'EOF'
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
six = "==1.16.0"
[pipenv]
use_pylock = true
EOF
pipenv lock && rm Pipfile.lock # pylock-only checkout
socket-patch scan --mode hosted --yes --json --ecosystems pypi \
--api-url $MOCK --org test-org --api-token x --patch-server-url $MOCK
# -> status success, redirected 1, rewrittenFiles ["pylock.toml"], no warnings
grep archive pylock.toml # archive = { url = "$MOCK/patch/pypi/six/1.16.0/<grant>/<uuid>/six-1.16.0-py2.py3-none-any.whl", hashes = {...} }
pipenv sync; echo $? # 0
pipenv run python -c "import six; print(getattr(six, 'SOCKET_PATCHED', False))" # False: the upstream sixVendored is the same with --mode vendored: archive = { path = ".socket/vendor/pypi/<uuid>/<wheel>" … }, then pipenv sync gives the upstream six, and socket-patch vex --product pkg:pypi/demo@1.0.0 --output vex.json attests not_affected.
Reproduced 2/2 for hosted on 2026.8.0, and 2/2 for vendored on 2026.8.0.
Expected vs actual
- Expected: docs/ecosystems.md lists PEP 751
pylock.tomlamong the hosted / vendored lock formats, and CLI_CONTRACT.md says a dep counts as redirected only when the reference "actually landed in a project file". The rewrite has to be one the project's installer honours. A rewrite that leaves the next frozen install on the unpatched bytes is a defect. When Pipenv is the consumer ofpylock.toml(a Pipfile beside it and noPipfile.lock), socket-patch should either write a shape Pipenv's converter keeps, or refuse / warn (e.g. aredirect_pipenv_*code telling the user to commit aPipfile.lock) and not count it as redirected or attest it. - Actual: success, no warning, and an unpatched install. In vendored mode, a false
not_affectedVEX.
OS × version
| Pipenv | pipenv sync after hosted rewrite of a pylock-only project |
Notes |
|---|---|---|
| 2025.1.3 | n/a | no pylock support; scan rewrites nothing (pass) |
| 2026.0.0, 2026.1.0, 2026.2.0, 2026.2.2 | exit 1 Pipfile.lock not found! |
loud failure, not silent |
| 2026.4.0, 2026.6.0, 2026.7.1, 2026.8.0 | exit 0, upstream six installed | fail |
2026.8.0, Pipfile.lock + pylock.toml both present |
PATCHED (sync, install --deploy, verify, requirements) |
pass |
Linux only. macOS / Windows probe branches could not be used this run (see ledger). The Pipenv code is pure Python, so the behaviour should be the same on every OS.
First bad Pipenv release: 2026.4.0 (the first release whose sync installs from a pylock-only project). socket-patch: current main 9c43dfc. I did not bisect socket-patch, because the pylock rewriter has never written a shape that Pipenv reads.
Suspect code
- The pylock rewriters and vendoring treat
pylock*.tomlas consumer-neutral (crates/socket-patch-core/src/utils/python_lock.rs:177is_python_lock_name, the hosted candidate list atcrates/socket-patch-core/src/hosted/engine.rs:372, vendoring incrates/socket-patch-core/src/vendor/pypi_lock.rs). Nothing checks whether a Pipfile beside the lock makes Pipenv the installer. - Pipenv side, for reference:
pipenv/utils/pylock.pyPylockFile.convert_to_pipenv_lockfile(2026.8.0) reads onlywheels[*].hashes.sha256andsdist.hashes.sha256.
Related but distinct: #479 (Hatch regenerates its pylock.toml).
Activity
mikolalysenko commentedon Oct 6, 2026
[agent] Triaged as priority:p1 (Pipenv / PyPI family). Not a duplicate: #612 and #769 are about Pipfile.lock wiring in vendored mode, #891 is about symlinked pylock files and #479 is Hatch regenerating its pylock. This one is the pylock rewriter not checking whether Pipenv is the consumer of a pylock-only project. No open PR covers it yet.
Generated by Claude Code
mikolalysenko commentedon Oct 8, 2026
[agent] Pipenv bug-hunt (ledger #313): the
pylock_namevariant also reproduces on mainb762f41, with Pipenv 2026.8.0 on Linux and py3.11.Shape: a Pipfile with
[pipenv] use_pylock = trueandpylock_name = "dev".pipenv lockwritespylock.dev.toml, and thenPipfile.lockis deleted so the project is pylock-only.pylock.dev.tomlpipenv syncvexarchive = { url = <hosted wheel>, hashes = {sha256 = …} }replacesindex+wheels;rewrittenFiles: ["pylock.dev.toml"], exit 0six.pyis the upstream releasearchive = { path = ".socket/vendor/pypi/<uuid>/six-1.16.0-py2.py3-none-any.whl", … }, exit 0not_affected(false)I ran it twice, on two fresh WORKON_HOME venvs. So the fix needs to cover any
pylock.<name>.tomlthat Pipenv consumes, not justpylock.toml.Generated by Claude Code
mikolalysenko commentedon Oct 9, 2026
[agent] Claiming this issue (with #1122; shared root cause: the pylock readers/rewriters and the vendored flavor router treat
pylock*.tomlas installer-neutral and never check whether aPipfilebeside it makes Pipenv the installer). Branch: agent/fix-pipenv-pylock-consumer. Claim-ID: 2026-10-09T00:20:38Z-68f1edGenerated by Claude Code
mikolalysenko commentedon Oct 9, 2026
[agent] Draft PR: #1193
Generated by Claude Code
Stop wiring pylocks that Pipenv installs from
Test Pipenv pylock projects with real Pipenv