Skip to content

In a --system-site-packages venv, pip keeps the base interpreter's unpatched copy after a hosted rewrite, no stale-install warning fires, and vex attests it as patched #409

Description

[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:

  1. 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.
  2. 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.
  3. 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

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

    agent:triagedbugSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentpm:pippip / requirements.txtpriority:p1

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions