Skip to content

Lockfile-only scan ignores pins in requirements.txt -r includes, so a fresh checkout reports "No patches available" and installs unpatched (hosted and vendored) #412

Description

[agent] Found by the scheduled pip / requirements.txt bug-hunt routine (ledger #309).

Summary

On a checkout with no virtualenv yet (the usual CI / fresh-clone case), scan only finds the pins written directly in the root requirements.txt. A pin reached through an -r include (-r requirements/base.txt, the common split-requirements layout) never joins discovery. Both scan (hosted) and scan --mode vendored print No patches available for installed packages. and exit 0 / success, with lockfileOnlyPackages: 0 and no warning. pip install -r requirements.txt then installs the unpatched upstream package.

With an installed .venv, the same project is vendored: the vendored writer rewrites the pin inside requirements/base.txt, and pip install -r requirements.txt gets the patched wheel. Only the lockfile-only inventory is missing the include.

Impact

Patches are silently skipped for every dependency pinned in an included file on lockfile-only runs. The scan reports success, so nothing tells the user the pins were never looked at.

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. Linux, CPython 3.11, pip 24.0, main 2463257.

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 p/requirements && cd p
printf -- '-r requirements/base.txt\n' > requirements.txt
printf 'six==1.16.0\n' > requirements/base.txt
socket-patch scan --mode vendored --json $A | jq '{status, lockfileOnlyPackages, packages: [.packages[].purl]}'
# {"status":"success","lockfileOnlyPackages":0,"packages":[]}
socket-patch scan --json $A | jq '{status, lockfileOnlyPackages}'     # hosted: same
python -m venv v && v/bin/pip install -r requirements.txt && v/bin/python -c 'import six; print(getattr(six,"SOCKET_PATCHED",0))'   # 0 → unpatched

Control: the same pin written directly in requirements.txt gives lockfileOnlyPackages: 1 and packages: ["pkg:pypi/[email protected]"], and is rewritten in both modes.

layout mode lockfileOnlyPackages rewritten pip install -r
root requirements.txt hosted 1 yes patched
root requirements.txt vendored 1 yes patched
-r requirements/base.txt hosted 0 no unpatched
-r requirements/base.txt vendored 0 no unpatched
-r requirements/base.txt + populated .venv vendored n/a (installed) yes, in base.txt patched

All Linux rows reproduced twice.

Expected vs actual

  • Expected: CLI_CONTRACT.md lists vendored pypi wiring as "requirements.txt + its in-root -r includes", and the hosted unwind coverage lists "requirements.txt (+ in-root -r includes)". The "Lockfile supplement (v3.4)" paragraph says pinned requirements.txt dependencies with no installed copy join discovery. The files the vendored writer edits should be the files the lockfile-only inventory reads.
  • Actual: only the root file is inventoried, so included pins are invisible until something is installed.

For hosted mode, rewriting only the root file is documented: an installed pin found only in an include gets redirect_requirements_entry_not_found. On a lockfile-only checkout, though, even that warning is missing, because the package never enters discovery.

OS × version

OS pip / Python include layout, hosted include layout, vendored root-file control
Linux (sandbox) 20.3.4/3.10, 23.3.2/3.11, 24.3.1/3.12, 26.2.1/3.13 (install of the untouched include project) not discovered, unpatched not discovered, unpatched patched
ubuntu-latest (probe) 20.3.4 / 3.8 not discovered, unpatched not discovered, unpatched patched
ubuntu-latest (probe) 25.0.1 / 3.8 not discovered, unpatched not discovered, unpatched patched
ubuntu-latest (probe) 26.2.1 / 3.13 not discovered, unpatched not discovered, unpatched patched
macos-latest (probe) 20.3.4 / 3.8 not discovered, unpatched not discovered, unpatched patched
macos-latest (probe) 25.0.1 / 3.8 not discovered, unpatched not discovered, unpatched patched
macos-latest (probe) 26.2.1 / 3.13 not discovered, unpatched not discovered, unpatched patched
windows-latest (probe) 20.3.4 / 3.8 not discovered, unpatched not discovered, unpatched patched
windows-latest (probe) 25.0.1 / 3.8 not discovered, unpatched not discovered, unpatched patched
windows-latest (probe) 26.2.1 / 3.13 not discovered, unpatched not discovered, unpatched patched
all 3 OS (probe) 20.3.4 / 3.13 blocked: pip 20.3.4 can't run on 3.13

First bad

Not a regression. v4.0.0 behaves the same: scan --mode vendored on the include layout vendors nothing.

Suspect code

crates/socket-patch-core/src/vendor/lock_inventory/pypi.rs:587-588: inventory_requirements_txt reads only view.read_text("requirements.txt") and skips every --prefixed line (-r / -c included) without following it. The vendored writer's include walk (vendor/pypi_requirements.rs:599-724, requirements_include_names) already resolves in-root includes and could be shared.

Probe run: https://github.com/SocketDev/socket-patch/actions/runs/36806309982

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