[agent] Found by the scheduled PDM bug-hunt routine (ledger #312).
Summary
PDM records the project's interpreter in .pdm-python. With venv.in_project = false it points at PDM's out-of-tree venv (<data_dir>/pdm/venvs/<project>-<hash>-<py>/bin/python), and after pdm use <path> it points at whatever venv the user picked. pdm sync, pdm install and pdm run all use that interpreter. The Python crawler's local discovery (find_local_venv_site_packages_with) has dedicated probes for Pipenv's and Poetry's out-of-tree venvs, but none for PDM. It only checks VIRTUAL_ENV, ./.venv and ./venv, and then falls back to the global interpreters. So on a PDM project whose env isn't ./.venv:
- Agent mode skips the real env and exits 0.
scan --mode agent reports [skip] … (not installed; run your package manager's install first …). The package is installed, in the env pdm run uses.
- Agent mode patches the wrong env and reports success. If a stray
./.venv exists (for example left over from before switching to in_project = false), or if python3 on PATH has the package, that tree is patched (applied: 1). PDM's env stays upstream.
- Hosted mode gives no stale-install warning, and VEX falsely attests. After
scan --mode hosted with no reinstall, the in-project control prints the still differ from the patched hashes warning, and vex omits the package as not_applied (exit 1). With the out-of-tree env there's no warning, and vex emits not_affected / inline_mitigations_already_exist ("redirected", exit 0) while pdm run python imports the unpatched bytes.
The Poetry and Pipenv versions of this were filed and fixed (#327, #329, #334, #384; #476 is open), so this is the PDM gap in the same probe.
Impact
Agent mode silently doesn't patch, or patches an environment the app never runs in, and still exits 0 with applied: 1. VEX then claims a mitigation that isn't installed. venv.in_project = false is a documented, commonly used PDM setting (centralized venvs), and pdm use <venv> is the standard way to bind an existing venv.
Repro (Linux, PDM 2.29.2, main 61cfb9b)
The patch API was a local mock serving the free urllib3@1.26.18 patch (SOCKET_PROXY_URL). Any PyPI patch will do.
mkdir app && cd app
printf '[project]\nname="app"\nversion="0.0.0"\nrequires-python=">=3.8"\ndependencies=["urllib3==1.26.18"]\n[tool.pdm]\ndistribution=false\n' > pyproject.toml
pdm config venv.in_project false
pdm lock && pdm sync
cat .pdm-python # …/pdm/venvs/app-XXXX-3.11/bin/python
# (2) optional: a stray in-project venv that PDM does not use
python3 -m venv .venv && .venv/bin/pip install urllib3==1.26.18
socket-patch scan --mode agent --yes --json | jq .apply
# no .venv: {"applied":0,"skipped":1,…} "[skip] … not installed"
# with .venv: {"applied":1,…} but:
pdm run python -c 'import urllib3.response as r; print(getattr(r,"SOCKET_PATCHED",0))' # 0 (upstream)
.venv/bin/python -c 'import urllib3.response as r; print(getattr(r,"SOCKET_PATCHED",0))' # 1 (wrong env patched)
# (3) hosted + VEX, no reinstall after the rewrite
git checkout pdm.lock; rm -rf .venv .socket
socket-patch scan --mode hosted --yes # no "installed files … still differ" warning
socket-patch vex --product pkg:pypi/app@0.0.0 # exit 0, status not_affected ("redirected")
pdm run python -c 'import urllib3.response as r; print(getattr(r,"SOCKET_PATCHED",0))' # 0
Expected vs actual
- Expected: the crawler finds the environment PDM installs into, as it already does for Poetry's and Pipenv's out-of-tree venvs (docs/ecosystems.md: "Pipenv's out-of-tree venv are discovered"; CLI_CONTRACT.md lists the probe set "VIRTUAL_ENV,
./.venv, ./venv, Pipenv's out-of-tree WORKON_HOME venv"). For a PDM project, .pdm-python (or PDM's venv.location / venv.in_project resolution) should decide the env, and a ./.venv that PDM doesn't use shouldn't be patched. A stale install must keep the hosted warning and the VEX not_applied omission, as in the in-project control.
- Actual: see the summary. Every command exits 0.
Matrix (Linux; each cell reproduced at least twice)
| PDM |
env setup |
agent scan |
wrong tree patched |
hosted stale warning / VEX |
| 2.29.2 |
venv.in_project=false |
skip "not installed", exit 0 |
— |
no warning / not_affected on an unpatched env |
| 2.29.2 |
in_project=false + stray ./.venv |
applied: 1 |
./.venv |
— |
| 2.29.2 |
in_project=false, python3 on PATH has urllib3 |
applied: 1 |
PATH interpreter |
— |
| 2.29.2 |
default in_project=true, pdm use -f <external venv>/bin/python |
skip "not installed", exit 0 |
— |
— |
| 2.12.4 |
venv.in_project=false (± stray .venv) |
skip / applied: 1 to ./.venv |
./.venv |
— |
| 2.29.2 control |
default in-project .venv |
applied to PDM's env |
— |
warning + not_applied (correct) |
macOS and Windows weren't probed. The probe is OS-independent (PDM's venv dir is under platformdirs' user data dir on every OS).
First bad version: not a regression. The published socket-patch 4.0.0 (PyPI) behaves the same (stray .venv patched, PDM env upstream). PDM discovery has never existed.
Suspect code
crates/socket-patch-core/src/crawlers/python_crawler.rs:336 (find_local_venv_site_packages_with): steps 2–3 special-case Pipenv and Poetry. There's no PDM step, so a PDM project falls through to ./.venv / ./venv (line 381) and then the global fallback. A fix would read .pdm-python (PDM ≥ 2.0; PDM 1.x stored python.path in .pdm.toml). When it names an interpreter, it would probe that interpreter's prefix and nothing else, the same as the Pipenv branch.
- The hosted stale-install probe and
vex use the same crawler, which is why (3) follows from it.
[agent] Found by the scheduled PDM bug-hunt routine (ledger #312).
Summary
PDM records the project's interpreter in
.pdm-python. Withvenv.in_project = falseit points at PDM's out-of-tree venv (<data_dir>/pdm/venvs/<project>-<hash>-<py>/bin/python), and afterpdm use <path>it points at whatever venv the user picked.pdm sync,pdm installandpdm runall use that interpreter. The Python crawler's local discovery (find_local_venv_site_packages_with) has dedicated probes for Pipenv's and Poetry's out-of-tree venvs, but none for PDM. It only checksVIRTUAL_ENV,./.venvand./venv, and then falls back to the global interpreters. So on a PDM project whose env isn't./.venv:scan --mode agentreports[skip] … (not installed; run your package manager's install first …). The package is installed, in the envpdm runuses../.venvexists (for example left over from before switching toin_project = false), or ifpython3onPATHhas the package, that tree is patched (applied: 1). PDM's env stays upstream.scan --mode hostedwith no reinstall, the in-project control prints thestill differ from the patched hasheswarning, andvexomits the package asnot_applied(exit 1). With the out-of-tree env there's no warning, andvexemitsnot_affected/inline_mitigations_already_exist("redirected", exit 0) whilepdm run pythonimports the unpatched bytes.The Poetry and Pipenv versions of this were filed and fixed (#327, #329, #334, #384; #476 is open), so this is the PDM gap in the same probe.
Impact
Agent mode silently doesn't patch, or patches an environment the app never runs in, and still exits 0 with
applied: 1. VEX then claims a mitigation that isn't installed.venv.in_project = falseis a documented, commonly used PDM setting (centralized venvs), andpdm use <venv>is the standard way to bind an existing venv.Repro (Linux, PDM 2.29.2, main
61cfb9b)The patch API was a local mock serving the free
urllib3@1.26.18patch (SOCKET_PROXY_URL). Any PyPI patch will do.Expected vs actual
./.venv,./venv, Pipenv's out-of-treeWORKON_HOMEvenv"). For a PDM project,.pdm-python(or PDM'svenv.location/venv.in_projectresolution) should decide the env, and a./.venvthat PDM doesn't use shouldn't be patched. A stale install must keep the hosted warning and the VEXnot_appliedomission, as in the in-project control.Matrix (Linux; each cell reproduced at least twice)
venv.in_project=falsein_project=false+ stray./.venvapplied: 1./.venvin_project=false,python3on PATH has urllib3applied: 1in_project=true,pdm use -f <external venv>/bin/pythonvenv.in_project=false(± stray.venv)applied: 1to./.venv./.venv.venvnot_applied(correct)macOS and Windows weren't probed. The probe is OS-independent (PDM's venv dir is under platformdirs' user data dir on every OS).
First bad version: not a regression. The published
socket-patch4.0.0 (PyPI) behaves the same (stray.venvpatched, PDM env upstream). PDM discovery has never existed.Suspect code
crates/socket-patch-core/src/crawlers/python_crawler.rs:336(find_local_venv_site_packages_with): steps 2–3 special-case Pipenv and Poetry. There's no PDM step, so a PDM project falls through to./.venv/./venv(line 381) and then the global fallback. A fix would read.pdm-python(PDM ≥ 2.0; PDM 1.x storedpython.pathin.pdm.toml). When it names an interpreter, it would probe that interpreter's prefix and nothing else, the same as the Pipenv branch.vexuse the same crawler, which is why (3) follows from it.