[agent] Found by the scheduled Poetry bug-hunt routine (ledger #311).
Summary
poetry_virtualenvs_path (crates/socket-patch-core/src/crawlers/python_crawler.rs:1003-1005) treats {project-dir} in virtualenvs.path as a placeholder for the project directory. Poetry has no such placeholder. Its Config.process() only substitutes keys that exist in its own config ({cache-dir}, {data-dir}, …), and project-dir isn't one of them:
- Poetry 1.8.5, 2.0.1 and 2.5.1 keep the text literally ("might be resolved later"), so the env is created under a directory literally named
{project-dir} inside the project: <proj>/{project-dir}/.envs/<name>-<hash>-pyX.Y.
- Poetry 1.1.15 turns it into an empty string, so the env lands in
/.envs/<name>-<hash>-pyX.Y.
Checked in poetry/config/config.py (process / virtualenvs_path) for 1.1.15, 1.8.5, 2.0.1 and 2.5.1, and confirmed with poetry env info -p.
socket-patch looks in <proj>/.envs instead, which doesn't exist. Discovery then falls through ./.venv and ./venv to the documented global-interpreter fallback.
Impact
With virtualenvs.path = "{project-dir}/.envs" in poetry.toml, scan --mode agent does three things:
- It patches whatever global copy matches. In this case that was the apt-owned
/usr/lib/python3/dist-packages/six.py (python3-six), a system package outside the project.
- It leaves Poetry's env unpatched:
poetry run python -c "import six" imports the upstream bytes.
- It reports
success, applied: 1, exit 0. socket-patch vex then writes a not_affected / inline_mitigations_already_exist statement for pkg:pypi/demo@0.1.0.
That is a VEX attestation for a patch the project's runtime doesn't have, plus an unrequested write into a distro-managed file. With no global copy present, the result is a silent package_not_installed skip with exit 0.
The trigger is a non-standard config value (Poetry doesn't document {project-dir}), but the code and its unit test (python_crawler.rs:3550-3556) encode it as supported. The fallout is the worst kind: a wrong-target write plus a false attestation.
Repro
Run as root (or anyone who can write the system six) on Linux, with Debian's python3-six 1.16.0 installed. Any global six==1.16.0 copy works too.
mkdir -p /tmp/pdr/proj/demo && cd /tmp/pdr/proj && touch demo/__init__.py
cat > pyproject.toml <<'EOF'
[tool.poetry]
name = "demo"
version = "0.1.0"
description = ""
authors = ["x <x@x.x>"]
[tool.poetry.dependencies]
python = "^3.10"
six = "1.16.0"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
EOF
printf '[virtualenvs]\npath = "{project-dir}/.envs"\n' > poetry.toml
poetry lock && poetry install
poetry env info -p # -> {project-dir}/.envs/demo-<hash>-py3.11 (literal dir under the project)
socket-patch scan --mode agent --yes # any pkg:pypi/six@1.16.0 patch (a local mock API was used here)
poetry run python -c 'import six; print(six.__file__, getattr(six, "SOCKET_PATCHED", 0))' # -> .../{project-dir}/.envs/.../six.py 0
grep -c SOCKET_PATCHED /usr/lib/python3/dist-packages/six.py # -> 1
socket-patch vex --output vex.json # exit 0, 1 statement, not_affected
Control: virtualenvs.path = ".envs" in the same setup patches Poetry's env (poetry run sees the patch) and leaves the system copy untouched.
Expected vs actual
- Expected: docs/testing/poetry-compatibility.md ("Mode notes") says agent mode follows Poetry's
EnvManager.get(), with placement "reproduced without running Poetry, from POETRY_*, the project's poetry.toml, the user config.toml…". So virtualenvs.path should resolve the way Poetry resolves it: an unknown {key} kept literally on Poetry ≥ 1.2 (empty on 1.1), and a relative result taken against the cwd. The env Poetry actually uses gets patched, or, if it can't be found, nothing outside the project is written and vex refuses.
- Actual:
{project-dir} is replaced with the cwd, the env is missed, the global copy is patched, and VEX attests.
Matrix (Linux, main 045d7ec)
| Poetry |
{project-dir}/.envs |
.envs (control) |
| 1.1.15 |
fail (env at /.envs/...; system six patched, vex attests), 2/2 runs |
not run |
| 1.8.5 |
fail (env at <proj>/{project-dir}/.envs/...), 2/2 runs |
pass (r5) |
| 2.5.1 |
fail (same), 2/2 runs |
pass |
| macOS / Windows |
untested (probe branches unavailable this run) |
|
Suspect code
crates/socket-patch-core/src/crawlers/python_crawler.rs:1003-1005: .replace("{project-dir}", &cwd.to_string_lossy()). Poetry has no such key. Other unknown {…} keys aren't modelled either; only {cache-dir} is a real Poetry substitution here.
crates/socket-patch-core/src/crawlers/python_crawler.rs:741: the doc comment calls {project-dir} one of "Poetry's placeholders".
- Unit test
python_crawler.rs:3550-3556 asserts the incorrect expansion.
- The global fallback in
find_local_venv_site_packages_with is what turns the miss into a write outside the project (documented behaviour, but it amplifies this).
[agent] Found by the scheduled Poetry bug-hunt routine (ledger #311).
Summary
poetry_virtualenvs_path(crates/socket-patch-core/src/crawlers/python_crawler.rs:1003-1005) treats{project-dir}invirtualenvs.pathas a placeholder for the project directory. Poetry has no such placeholder. ItsConfig.process()only substitutes keys that exist in its own config ({cache-dir},{data-dir}, …), andproject-dirisn't one of them:{project-dir}inside the project:<proj>/{project-dir}/.envs/<name>-<hash>-pyX.Y./.envs/<name>-<hash>-pyX.Y.Checked in
poetry/config/config.py(process/virtualenvs_path) for 1.1.15, 1.8.5, 2.0.1 and 2.5.1, and confirmed withpoetry env info -p.socket-patch looks in
<proj>/.envsinstead, which doesn't exist. Discovery then falls through./.venvand./venvto the documented global-interpreter fallback.Impact
With
virtualenvs.path = "{project-dir}/.envs"inpoetry.toml,scan --mode agentdoes three things:/usr/lib/python3/dist-packages/six.py(python3-six), a system package outside the project.poetry run python -c "import six"imports the upstream bytes.success,applied: 1, exit 0.socket-patch vexthen writes anot_affected/inline_mitigations_already_existstatement forpkg:pypi/demo@0.1.0.That is a VEX attestation for a patch the project's runtime doesn't have, plus an unrequested write into a distro-managed file. With no global copy present, the result is a silent
package_not_installedskip with exit 0.The trigger is a non-standard config value (Poetry doesn't document
{project-dir}), but the code and its unit test (python_crawler.rs:3550-3556) encode it as supported. The fallout is the worst kind: a wrong-target write plus a false attestation.Repro
Run as root (or anyone who can write the system six) on Linux, with Debian's
python3-six1.16.0 installed. Any globalsix==1.16.0copy works too.Control:
virtualenvs.path = ".envs"in the same setup patches Poetry's env (poetry runsees the patch) and leaves the system copy untouched.Expected vs actual
EnvManager.get(), with placement "reproduced without running Poetry, fromPOETRY_*, the project'spoetry.toml, the userconfig.toml…". Sovirtualenvs.pathshould resolve the way Poetry resolves it: an unknown{key}kept literally on Poetry ≥ 1.2 (empty on 1.1), and a relative result taken against the cwd. The env Poetry actually uses gets patched, or, if it can't be found, nothing outside the project is written andvexrefuses.{project-dir}is replaced with the cwd, the env is missed, the global copy is patched, and VEX attests.Matrix (Linux, main
045d7ec){project-dir}/.envs.envs(control)/.envs/...; system six patched, vex attests), 2/2 runs<proj>/{project-dir}/.envs/...), 2/2 runsSuspect code
crates/socket-patch-core/src/crawlers/python_crawler.rs:1003-1005:.replace("{project-dir}", &cwd.to_string_lossy()). Poetry has no such key. Other unknown{…}keys aren't modelled either; only{cache-dir}is a real Poetry substitution here.crates/socket-patch-core/src/crawlers/python_crawler.rs:741: the doc comment calls{project-dir}one of "Poetry's placeholders".python_crawler.rs:3550-3556asserts the incorrect expansion.find_local_venv_site_packages_withis what turns the miss into a write outside the project (documented behaviour, but it amplifies this).