[agent] Found by the scheduled Poetry bug-hunt routine (ledger #311).
Summary
With virtualenvs.in-project = true set and no ./.venv directory, Poetry keeps installing into the project's existing out-of-tree env (~/.cache/pypoetry/virtualenvs/<name>-<hash>-py3.X). That is what happens when a user turns on poetry config virtualenvs.in-project true (global or --local) in a project that already has a Poetry env. poetry env info -p and poetry install both keep using the old env until it is removed.
socket-patch reads an explicit in-project = true as "only ./.venv" and never probes Poetry's out-of-tree env. So:
- Agent mode (
scan --mode agent) falls back to the global interpreter. If that interpreter has the same release (here a pip install --user six==1.16.0), it patches the global copy and reports added, exit 0. Otherwise it reports skipped / package_not_installed, exit 0. Either way, the env Poetry installed into stays unpatched.
- Hosted mode (
scan --mode hosted --vex): the v5 installed-bytes check (redirect_pypi_stale_install) never runs because no project venv is found. The same-run VEX then attests not_affected / inline_mitigations_already_exist while Poetry's env still imports upstream six.
This is the mirror image of #327 case 3 (in-project = false + stray .venv), which #330 fixed. #330 kept this side: uses_in_project_venv returns the explicit setting without checking for ./.venv, and poetry_virtualenvs_root returns None for in_project == Some(true).
Impact
- A false VEX attestation in the default (hosted) mode. A consumer is told the vulnerability is mitigated while the running code is unpatched.
- Agent mode can write to the user's global site-packages, which is outside the project, while reporting success.
- This is a common configuration. Setting
virtualenvs.in-project true globally is a popular tweak, and Poetry silently keeps using the pre-existing cache env, so users don't notice that .venv was never created.
Repro (Linux, Poetry 2.5.1, Python 3.11)
The mock patch API serves one patch for pkg:pypi/six@1.16.0 that appends SOCKET_PATCHED = 1 to six.py. It uses the same routes as tests/vex_pypi_real_common/mod.rs (batch, view, the /patches/package grant and the wheel) plus /patches/blob/<hash>. $A = --api-url http://127.0.0.1:18766 --api-token fake --org test-org --patch-server-url http://127.0.0.1:18766.
mkdir ipapp && cd ipapp
cat > pyproject.toml <<'TOML'
[tool.poetry]
name = "ipapp"
version = "0.1.0"
description = ""
authors = ["x <x@x>"]
[tool.poetry.dependencies]
python = "^3.9"
six = "1.16.0"
TOML
poetry install --no-root # default config: ~/.cache/pypoetry/virtualenvs/ipapp-<hash>-py3.11
poetry config virtualenvs.in-project true # (or --local; same result)
poetry install --no-root # Poetry keeps using the existing out-of-tree env
poetry env info -p # -> ~/.cache/pypoetry/virtualenvs/ipapp-<hash>-py3.11 ; no ./.venv
E=$(poetry env info -p)
# Agent mode
socket-patch scan --mode agent --yes --json --ecosystems pypi $A # scannedPackages 105 (global interpreter), action "added", exit 0
tail -1 $E/lib/python3.11/site-packages/six.py # upstream line: NOT patched
tail -1 ~/.local/lib/python3.11/site-packages/six.py # SOCKET_PATCHED = 1 <- the global copy got patched
# (with no global six: action "skipped", errorCode "package_not_installed", exit 0)
# Hosted mode (restore everything first; the env still holds upstream six)
socket-patch scan --mode hosted --yes --json --ecosystems pypi --vex vex.json --vex-product pkg:pypi/ipapp@0.1.0 $A
# -> redirected: 1, no redirect_pypi_stale_install warning, exit 0
jq '.statements[] | [.status, .justification]' vex.json # ["not_affected","inline_mitigations_already_exist"]
$E/bin/python -c 'import six; print(hasattr(six, "SOCKET_PATCHED"))' # False
Control: the same project without in-project = true (or with poetry.toml removed) gets added in Poetry's env, and hosted emits redirect_pypi_stale_install / vex_omitted for the stale env.
Expected vs actual
- Expected: docs/testing/poetry-compatibility.md ("Mode notes") says the crawler probes "the virtualenv(s) Poetry placed under its
virtualenvs.path" for "a Poetry project whose ./.venv Poetry would not use (an explicit virtualenvs.in-project = false, or no ./.venv at all)". The same section says hosted's stale check "excludes the package from same-run VEX". Poetry's own EnvManager.get() returns ./.venv only if it exists (in_project_venv_exists()), and otherwise returns <virtualenvs.path>/<name>-<hash>-py<minor> whenever that exists, whatever in-project says.
- Actual: with
in-project = true and no ./.venv, the Poetry env is never probed. Agent mode patches the wrong interpreter or skips with exit 0, and hosted VEX attests from the lock pin alone.
OS × version
| OS |
Poetry |
in-project = true in |
Agent: Poetry env |
Agent: global user-site six |
Hosted --vex with a stale env |
| Linux |
1.1.15 (py3.9) |
poetry.toml |
❌ unpatched |
❌ patched instead |
❌ not_affected (plus the redirect_poetry_stale_install_risk advisory) |
| Linux |
1.8.5 |
poetry.toml |
❌ |
❌ |
❌ not_affected |
| Linux |
2.0.1 |
poetry.toml |
❌ |
❌ |
❌ not_affected |
| Linux |
2.5.1 |
poetry.toml (2/2) |
❌ |
❌ |
❌ not_affected |
| Linux |
2.5.1 |
user config.toml (2/2) |
❌ |
❌ |
not run |
| macOS / Windows |
— |
— |
not run: this routine can't use probe branches this run |
|
|
The logic is platform-independent (config precedence only).
First bad version
It never worked. Release 4.0.0 has no Poetry out-of-tree discovery. #241 added it and #330 (5678b76) reworked it, and both skip the out-of-tree root when in_project == Some(true). Main 6e7ef74 reproduces.
Suspect code
crates/socket-patch-core/src/crawlers/python_crawler.rs:634 PoetryProject::uses_in_project_venv: returns an explicit in_project as-is. Poetry's get() only takes ./.venv when the directory exists (in_project_venv_exists), and otherwise falls through to the out-of-tree env.
crates/socket-patch-core/src/crawlers/python_crawler.rs:571 poetry_virtualenvs_root: config.in_project == Some(true) returns None, so even a direct call can't find the env.
crates/socket-patch-cli/src/commands/scan/hosted/python.rs:40-56: the stale-install check relies on find_local_venv_site_packages, so it silently has no evidence and VEX attests.
Related
[agent] Found by the scheduled Poetry bug-hunt routine (ledger #311).
Summary
With
virtualenvs.in-project = trueset and no./.venvdirectory, Poetry keeps installing into the project's existing out-of-tree env (~/.cache/pypoetry/virtualenvs/<name>-<hash>-py3.X). That is what happens when a user turns onpoetry config virtualenvs.in-project true(global or--local) in a project that already has a Poetry env.poetry env info -pandpoetry installboth keep using the old env until it is removed.socket-patch reads an explicit
in-project = trueas "only./.venv" and never probes Poetry's out-of-tree env. So:scan --mode agent) falls back to the global interpreter. If that interpreter has the same release (here apip install --user six==1.16.0), it patches the global copy and reportsadded, exit 0. Otherwise it reportsskipped/package_not_installed, exit 0. Either way, the env Poetry installed into stays unpatched.scan --mode hosted --vex): the v5 installed-bytes check (redirect_pypi_stale_install) never runs because no project venv is found. The same-run VEX then attestsnot_affected/inline_mitigations_already_existwhile Poetry's env still imports upstreamsix.This is the mirror image of #327 case 3 (
in-project = false+ stray.venv), which #330 fixed. #330 kept this side:uses_in_project_venvreturns the explicit setting without checking for./.venv, andpoetry_virtualenvs_rootreturnsNoneforin_project == Some(true).Impact
virtualenvs.in-project trueglobally is a popular tweak, and Poetry silently keeps using the pre-existing cache env, so users don't notice that.venvwas never created.Repro (Linux, Poetry 2.5.1, Python 3.11)
The mock patch API serves one patch for
pkg:pypi/six@1.16.0that appendsSOCKET_PATCHED = 1tosix.py. It uses the same routes astests/vex_pypi_real_common/mod.rs(batch, view, the/patches/packagegrant and the wheel) plus/patches/blob/<hash>.$A=--api-url http://127.0.0.1:18766 --api-token fake --org test-org --patch-server-url http://127.0.0.1:18766.Control: the same project without
in-project = true(or withpoetry.tomlremoved) getsaddedin Poetry's env, and hosted emitsredirect_pypi_stale_install/vex_omittedfor the stale env.Expected vs actual
virtualenvs.path" for "a Poetry project whose./.venvPoetry would not use (an explicitvirtualenvs.in-project = false, or no./.venvat all)". The same section says hosted's stale check "excludes the package from same-run VEX". Poetry's ownEnvManager.get()returns./.venvonly if it exists (in_project_venv_exists()), and otherwise returns<virtualenvs.path>/<name>-<hash>-py<minor>whenever that exists, whateverin-projectsays.in-project = trueand no./.venv, the Poetry env is never probed. Agent mode patches the wrong interpreter or skips with exit 0, and hosted VEX attests from the lock pin alone.OS × version
in-project = trueinsix--vexwith a stale envpoetry.tomlnot_affected(plus theredirect_poetry_stale_install_riskadvisory)poetry.tomlnot_affectedpoetry.tomlnot_affectedpoetry.toml(2/2)not_affectedconfig.toml(2/2)The logic is platform-independent (config precedence only).
First bad version
It never worked. Release 4.0.0 has no Poetry out-of-tree discovery. #241 added it and #330 (
5678b76) reworked it, and both skip the out-of-tree root whenin_project == Some(true). Main6e7ef74reproduces.Suspect code
crates/socket-patch-core/src/crawlers/python_crawler.rs:634PoetryProject::uses_in_project_venv: returns an explicitin_projectas-is. Poetry'sget()only takes./.venvwhen the directory exists (in_project_venv_exists), and otherwise falls through to the out-of-tree env.crates/socket-patch-core/src/crawlers/python_crawler.rs:571poetry_virtualenvs_root:config.in_project == Some(true)returnsNone, so even a direct call can't find the env.crates/socket-patch-cli/src/commands/scan/hosted/python.rs:40-56: the stale-install check relies onfind_local_venv_site_packages, so it silently has no evidence and VEX attests.Related
in-project = false+ stray.venvside of the same decision.