[agent] Found by the scheduled pip / requirements.txt bug-hunt routine (ledger #309).
Summary
In a virtualenv created with --system-site-packages (include-system-site-packages = true in pyvenv.cfg), the Python crawler only looks in the venv's own site-packages. Packages the venv's interpreter actually imports from the base interpreter are invisible to it: Debian/Ubuntu /usr/lib/python3/dist-packages, /usr/local/lib/python3.X/dist-packages, the user site, and a base install's site-packages. This has three effects after a hosted (default) scan of requirements.txt:
pip install -r requirements.txt sees six==1.16.0 already satisfied by the base interpreter's copy. It downloads the hosted wheel but does not install it, and exits 0. The venv keeps importing the unpatched base copy.
- The Python stale-install guard doesn't fire, so there is no
redirect_pypi_stale_install. It does fire for the same unpatched copy inside a plain venv.
socket-patch vex finds "nothing installed", so it attests from the lockfile pin: not_affected / inline_mitigations_already_exist, "Patched via Socket patch …", for code that is not patched.
Vendored mode has the same blind spot. pip keeps the base copy, and vex's documented vendored_tree_out_of_sync warning ("the installed tree does not match its vendored artifact") is missing, while it does print for the same situation in a plain venv. Agent mode reports the package as not installed; run your package manager's install first. Running the install doesn't change that, because pip says the requirement is already satisfied.
Impact
A false VEX attestation for an unpatched dependency, with no warning anywhere in the flow. This is a common layout: Debian/Ubuntu images ship python3-six, python3-yaml, python3-requests and others as distro packages, and python3 -m venv --system-site-packages is a standard way to reuse them.
Repro
Uses a local mock of the patch API that serves a patched six-1.16.0 wheel, the same shape as tests/vex_pypi_real_common::RealApi. It runs on Linux (Debian-based sandbox, where /usr/lib/python3/dist-packages/six.py is 1.16.0). The probe reproduces it portably by running python -m pip install six==1.16.0 into the base interpreter.
A="--api-url http://127.0.0.1:8765 --api-token fake --org test-org --patch-server-url http://127.0.0.1:8765"
mkdir p && cd p && printf 'six==1.16.0\n' > requirements.txt
python3 -m venv --system-site-packages .venv
socket-patch scan --json $A | jq '.redirect | {redirected, warnings}' # {"redirected":1,"warnings":[]}
.venv/bin/pip install -r requirements.txt; echo $? # Collecting six@ http://…/six-1.16.0-py2.py3-none-any.whl … → 0
.venv/bin/python -c 'import six; print(six.__file__, getattr(six,"SOCKET_PATCHED",0))'
# /usr/lib/python3/dist-packages/six.py 0 ← unpatched
socket-patch vex $A --product pkg:pypi/app@1 --output vex.json
# Wrote OpenVEX document with 1 statement to vex.json
jq '.statements[] | {status, justification, impact_statement}' vex.json
# "not_affected", "inline_mitigations_already_exist", "Patched via Socket patch 5a6b7c8d-… (redirected)"
Control: the same steps in a plain python3 -m venv .venv with upstream six==1.16.0 installed. The scan warns redirect_pypi_stale_install, and vex exits 1 with No applied patches with vulnerability metadata to attest. That's the documented behaviour.
Expected vs actual
- Expected: CLI_CONTRACT.md, "Verification basis", says that for hosted wiring "The installed copies the build consumes through the hosted wiring are hash-verified when any exist … Installed evidence wins:
hash_mismatch / not_applied are omitted", and that "not installed" has to mean the crawler looked. The "Python stale-install guard" paragraph says a readable file that differs from the patch's afterHash emits redirect_pypi_stale_install. The venv imports the base copy, so that copy is the one the build consumes.
- Actual: the crawler never looks at it.
vex treats the package as not installed and attests from the pin, and the guard stays silent.
OS × version
| OS |
Python / pip |
pip installs patched? |
stale warning |
vex |
| Linux (sandbox, Debian dist-packages) |
3.10 / 20.3.4 |
no (exit 0) |
none |
attests not_affected |
| Linux (sandbox) |
3.11 / 23.3.2 |
no (exit 0) |
none |
attests not_affected |
| Linux (sandbox) |
3.12 / 24.3.1 |
no (exit 0) |
none |
attests not_affected |
| Linux (sandbox) |
3.13 / 26.2.1 |
no (exit 0) |
none |
attests not_affected |
ubuntu-latest (probe, six in base site-packages) |
3.8 / 20.3.4 |
no (exit 0; imports /opt/hostedtoolcache/Python/3.x/…/site-packages/six.py) |
none (hosted and vendored) |
attests not_affected (hosted and vendored) |
ubuntu-latest (probe, six in base site-packages) |
3.8 / 25.0.1 |
no (exit 0; imports /opt/hostedtoolcache/Python/3.x/…/site-packages/six.py) |
none (hosted and vendored) |
attests not_affected (hosted and vendored) |
ubuntu-latest (probe, six in base site-packages) |
3.13 / 26.2.1 |
no (exit 0; imports /opt/hostedtoolcache/Python/3.x/…/site-packages/six.py) |
none (hosted and vendored) |
attests not_affected (hosted and vendored) |
macos-latest (probe, six in base site-packages) |
3.8 / 20.3.4 |
no (exit 0; imports /Library/Frameworks/Python.framework/…/site-packages/six.py) |
none (hosted and vendored) |
attests not_affected (hosted and vendored) |
macos-latest (probe, six in base site-packages) |
3.8 / 25.0.1 |
no (exit 0; imports /Library/Frameworks/Python.framework/…/site-packages/six.py) |
none (hosted and vendored) |
attests not_affected (hosted and vendored) |
macos-latest (probe, six in base site-packages) |
3.13 / 26.2.1 |
no (exit 0; imports /Library/Frameworks/Python.framework/…/site-packages/six.py) |
none (hosted and vendored) |
attests not_affected (hosted and vendored) |
windows-latest (probe, six in base site-packages) |
3.8 / 20.3.4 |
no (exit 0; imports C:\hostedtoolcache\windows\Python\…\site-packages\six.py) |
none (hosted and vendored) |
attests not_affected (hosted and vendored) |
windows-latest (probe, six in base site-packages) |
3.8 / 25.0.1 |
no (exit 0; imports C:\hostedtoolcache\windows\Python\…\site-packages\six.py) |
none (hosted and vendored) |
attests not_affected (hosted and vendored) |
windows-latest (probe, six in base site-packages) |
3.13 / 26.2.1 |
no (exit 0; imports C:\hostedtoolcache\windows\Python\…\site-packages\six.py) |
none (hosted and vendored) |
attests not_affected (hosted and vendored) |
| all 3 OS (probe) |
3.13 / 20.3.4 |
blocked: pip 20.3.4 can't run on 3.13 |
|
|
On Linux, the 3.11 / 24.0 cell also reproduced on two separate runs.
First bad
The false VEX attestation is new in 2463257 (#277, "with nothing installed … attests from that pin"). On v4.0.0 the same project gets omitted: pkg:pypi/[email protected] (package_not_found) and no attestation. The blind spot in the crawler predates that.
Suspect code
crates/socket-patch-core/src/crawlers/python_crawler.rs:274 (find_local_venv_site_packages) returns only <venv>/lib/python3.*/site-packages (and Lib/site-packages). It never reads pyvenv.cfg's include-system-site-packages, and get_site_packages_paths (:1395) returns early once a venv is found.
crates/socket-patch-cli/src/commands/vex.rs:582-600: the hosted "nothing installed → attest from lock pin" excuse relies on that crawl being complete.
Probe run: https://github.com/SocketDev/socket-patch/actions/runs/36806312653
[agent] Found by the scheduled pip / requirements.txt bug-hunt routine (ledger #309).
Summary
In a virtualenv created with
--system-site-packages(include-system-site-packages = trueinpyvenv.cfg), the Python crawler only looks in the venv's ownsite-packages. Packages the venv's interpreter actually imports from the base interpreter are invisible to it: Debian/Ubuntu/usr/lib/python3/dist-packages,/usr/local/lib/python3.X/dist-packages, the user site, and a base install'ssite-packages. This has three effects after a hosted (default) scan ofrequirements.txt:pip install -r requirements.txtseessix==1.16.0already satisfied by the base interpreter's copy. It downloads the hosted wheel but does not install it, and exits 0. The venv keeps importing the unpatched base copy.redirect_pypi_stale_install. It does fire for the same unpatched copy inside a plain venv.socket-patch vexfinds "nothing installed", so it attests from the lockfile pin:not_affected/inline_mitigations_already_exist, "Patched via Socket patch …", for code that is not patched.Vendored mode has the same blind spot. pip keeps the base copy, and
vex's documentedvendored_tree_out_of_syncwarning ("the installed tree does not match its vendored artifact") is missing, while it does print for the same situation in a plain venv. Agent mode reports the package asnot installed; run your package manager's install first. Running the install doesn't change that, because pip says the requirement is already satisfied.Impact
A false VEX attestation for an unpatched dependency, with no warning anywhere in the flow. This is a common layout: Debian/Ubuntu images ship
python3-six,python3-yaml,python3-requestsand others as distro packages, andpython3 -m venv --system-site-packagesis a standard way to reuse them.Repro
Uses a local mock of the patch API that serves a patched
six-1.16.0wheel, the same shape astests/vex_pypi_real_common::RealApi. It runs on Linux (Debian-based sandbox, where/usr/lib/python3/dist-packages/six.pyis 1.16.0). The probe reproduces it portably by runningpython -m pip install six==1.16.0into the base interpreter.Control: the same steps in a plain
python3 -m venv .venvwith upstreamsix==1.16.0installed. The scan warnsredirect_pypi_stale_install, andvexexits 1 withNo applied patches with vulnerability metadata to attest. That's the documented behaviour.Expected vs actual
hash_mismatch/not_appliedare omitted", and that "not installed" has to mean the crawler looked. The "Python stale-install guard" paragraph says a readable file that differs from the patch'safterHashemitsredirect_pypi_stale_install. The venv imports the base copy, so that copy is the one the build consumes.vextreats the package as not installed and attests from the pin, and the guard stays silent.OS × version
vexnot_affectednot_affectednot_affectednot_affectedsite-packages)/opt/hostedtoolcache/Python/3.x/…/site-packages/six.py)not_affected(hosted and vendored)site-packages)/opt/hostedtoolcache/Python/3.x/…/site-packages/six.py)not_affected(hosted and vendored)site-packages)/opt/hostedtoolcache/Python/3.x/…/site-packages/six.py)not_affected(hosted and vendored)site-packages)/Library/Frameworks/Python.framework/…/site-packages/six.py)not_affected(hosted and vendored)site-packages)/Library/Frameworks/Python.framework/…/site-packages/six.py)not_affected(hosted and vendored)site-packages)/Library/Frameworks/Python.framework/…/site-packages/six.py)not_affected(hosted and vendored)site-packages)C:\hostedtoolcache\windows\Python\…\site-packages\six.py)not_affected(hosted and vendored)site-packages)C:\hostedtoolcache\windows\Python\…\site-packages\six.py)not_affected(hosted and vendored)site-packages)C:\hostedtoolcache\windows\Python\…\site-packages\six.py)not_affected(hosted and vendored)On Linux, the 3.11 / 24.0 cell also reproduced on two separate runs.
First bad
The false VEX attestation is new in
2463257(#277, "with nothing installed … attests from that pin"). On v4.0.0 the same project getsomitted: pkg:pypi/[email protected] (package_not_found)and no attestation. The blind spot in the crawler predates that.Suspect code
crates/socket-patch-core/src/crawlers/python_crawler.rs:274(find_local_venv_site_packages) returns only<venv>/lib/python3.*/site-packages(andLib/site-packages). It never readspyvenv.cfg'sinclude-system-site-packages, andget_site_packages_paths(:1395) returns early once a venv is found.crates/socket-patch-cli/src/commands/vex.rs:582-600: the hosted "nothing installed → attest from lock pin" excuse relies on that crawl being complete.Probe run: https://github.com/SocketDev/socket-patch/actions/runs/36806312653