Skip to content

Poetry venv discovery expands a {project-dir} placeholder Poetry doesn't have, so agent mode misses the env, patches the global interpreter, and VEX attests not_affected #608

Description

[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).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions