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/six@1.16.0 (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

  1. mikolalysenko commented on Oct 1, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triaged: priority:p1 (pip). It's a false VEX attestation, so it's a correctness/security bug. Not a duplicate, and no open or merged PR covers it. The cause is in the Python crawler: find_local_venv_site_packages never reads include-system-site-packages from pyvenv.cfg, and get_site_packages_paths returns early once it finds a venv. That's a different problem from the Pipenv (#334/#384, PR #388) and Poetry (#327/#329, PR #330) venv-selection clusters. Those choose the wrong venv. This one picks the right venv but never looks at the base interpreter's site dirs behind it.


    Generated by Claude Code

  2. mikolalysenko commented on Oct 2, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] From the scheduled Poetry bug-hunt routine (ledger #311): Poetry 2.x users hit the agent-mode side of this through a Poetry setting, virtualenvs.options.system-site-packages = true. Tested on main 61cfb9b, Linux, Debian /usr/lib/python3/dist-packages/six.py 1.16.0. Reproduced 2/2.

    printf '[virtualenvs]\nin-project = true\n[virtualenvs.options]\nsystem-site-packages = true\n' > poetry.toml
    # pyproject: package-mode = false, six = "1.16.0"
    poetry lock && poetry install        # 2.5.1: "No dependencies to install or update"
    poetry run python -c 'import six; print(six.__file__)'   # /usr/lib/python3/dist-packages/six.py
    socket-patch scan --mode agent --yes $A --ecosystems pypi
    # [skip] pkg:pypi/six@1.16.0 (not installed; run your package manager's install first ...)  -> exit 0
    Poetry poetry install puts six in .venv? agent scan hosted scan → poetry install → vex
    2.5.1 no (system copy counts as installed) package_not_installed, exit 0, copy the app imports stays unpatched pass: Poetry sees the source change ("Updating six (1.16.0 -> 1.16.0 )") and installs the patched wheel into .venv; vex verifies it
    1.8.5 yes pass (patches .venv) pass, and redirect_pypi_stale_install fires before the reinstall

    So on Poetry the hosted path is protected by Poetry's own reinstall, and the false hosted VEX from the pip repro doesn't happen here. The agent-mode miss does. Advising "run your package manager's install first" can't help, because poetry install has nothing to do. Agent vex correctly finds nothing to attest.


    Generated by Claude Code

  3. mikolalysenko commented on Oct 5, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] From the scheduled Pipenv bug-hunt routine (ledger #313): Pipenv users hit the hosted and vendored false VEX through a Pipenv setting, pipenv --site-packages / PIPENV_SITE_PACKAGES=1. Unlike Poetry 2.x, Pipenv doesn't protect the hosted path, and it fails even in a fresh venv created after the rewrite.

    Tested on main 045d7ec, Linux. six 1.16.0 was installed into the base interpreter (a uv-managed CPython). I used a local mock patch API serving a patched six-1.16.0 wheel. Every cell reproduced 2/2.

    # Pipfile: six = "==1.16.0"; Pipfile.lock from `pipenv lock`; no venv yet (lock-only checkout)
    socket-patch scan --mode hosted --yes          # or: scan --vendor --yes   → Pipfile.lock six → "file": <hosted / .socket/vendor wheel>
    PIPENV_SITE_PACKAGES=1 pipenv --python "$BASE_PY"   # same as `pipenv --site-packages`
    pipenv install --deploy                        # exit 0; pip treats six==1.16.0 as already satisfied by the base copy
    pipenv run python -c 'import six; print(six.__file__, getattr(six, "SOCKET_PATCHED", 0))'
    # <base>/lib/python3.x/site-packages/six.py 0    ← unpatched
    socket-patch scan --mode hosted --yes          # no stale-install warning
    socket-patch vex --product pkg:pypi/app@1      # "status": "not_affected"  ← false
    Pipenv (Python) mode install --deploy in a fresh --site-packages venv stale warning on re-scan vex
    2018.11.26 (3.8) hosted unpatched base copy none not_affected
    2022.12.19 (3.8) hosted unpatched base copy none not_affected
    2023.12.1 (3.11) hosted unpatched base copy none not_affected
    2026.8.0 (3.11) hosted unpatched base copy none not_affected
    2018.11.26 (3.8) vendored unpatched base copy none (no vendored_tree_out_of_sync either) not_affected
    2026.8.0 (3.11) vendored unpatched base copy none not_affected
    2026.8.0 (3.11), warm --site-packages venv agent No patches selected n/a refuses (nothing to attest), correct
    2026.8.0 (3.11), control: plain venv, no --site-packages hosted patched none not_affected, correct

    Pipenv-specific notes:

    • With a warm venv the result is the same: pipenv sync and pipenv install --deploy both keep the base copy.
    • The stale-install remedy Pipenv users get elsewhere (pipenv run pip uninstall -y six && pipenv sync) would uninstall from the venv's view. It can't help here, because six isn't in the venv at all, and nothing tells the user to run it.
    • Pipenv's venv discovery (find_pipenv_virtualenv_site_packages) finds the right WORKON_HOME venv. As in the original report, the gap is that its pyvenv.cfg include-system-site-packages = true is never read.

    Generated by Claude Code

  4. mikolalysenko commented on Oct 9, 2026

    @mikolalysenko
    CollaboratorAuthor

    v5 triage: P2, not a release blocker. Retain inherited system-site-packages detection at P2; the ordinary isolated-venv and lockfile-first paths take precedence.

    This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions