GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Hosted requirements.txt rewrite skips PEP 440-equivalent pins like `six==1.16` for an installed 1.16.0, so `scan` exits 0 and pip installs the unpatched release (regression from v4.0.0) · Issue #475 · SocketDev/socket-patch

Hosted requirements.txt rewrite skips PEP 440-equivalent pins like six==1.16 for an installed 1.16.0, so scan exits 0 and pip installs the unpatched release (regression from v4.0.0) #475

New issue
Copy link
New issue
Copy link
Closed
#478
Closed
Hosted requirements.txt rewrite skips PEP 440-equivalent pins like six==1.16 for an installed 1.16.0, so scan exits 0 and pip installs the unpatched release (regression from v4.0.0)#475
#478
Copy link
Labels
agent:triagedbugSomething isn't workingSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentFound by a scheduled package-manager bug-hunt agentpm:pippip / requirements.txtpip / requirements.txtpriority:p1

Description

@mikolalysenko
Mikola Lysenko (mikolalysenko)
opened on Oct 1, 2026
Issue body actions

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

Summary

The hosted requirements.txt rewriter compares the pinned version to the patch version as a raw string. pip compares them under PEP 440, where trailing zeros, leading zeros and case don't matter. As a result, a pin that pip resolves to exactly the patched release is treated as "no entry":

requirements.txt what pip installs hosted rewrite on main
six==1.16.0 six 1.16.0 rewritten to six @ <hosted wheel> --hash=… ✅
six==1.16 six 1.16.0 left alone, redirect_requirements_entry_not_found
six==1.16.0.0 six 1.16.0 left alone, redirect_requirements_entry_not_found
Six==01.16.0 six 1.16.0 left alone, redirect_requirements_entry_not_found

scan --mode hosted (and get <uuid> --mode hosted) still finds the patch for the installed pkg:pypi/[email protected]. It then reports "redirected": 0 with that warning and exits 0 with status: success. The next pip install -r requirements.txt installs the unpatched upstream 1.16.0.

Short pins like ==X.Y for an X.Y.0 release are common in hand-written requirements files (for example urllib3==2.0, numpy==1.26).

The released v4.0.0 rewrites six==1.16 correctly, so this is a regression.

Impact

  • The project is silently left unpatched. Exit 0 with status: success, and the only signal is a warning that says there is no requirements.txt entry for six, even though there is one.
  • vex correctly attests nothing, so nothing downstream flags it either.
  • The vendored path (vendor / scan --mode vendored) has the same string comparison. It fails closed rather than silently: pypi_requirement_not_pinned: requirements.txt: six is not pinned to ==1.16.0, even though it is pinned to 1.16.0 under PEP 440. That's the "refusal fires when it shouldn't" case.

Repro (Linux, real pip)

The repro uses a local mock of the patch API (batch, by-package and package routes) that grants a hosted wheel for pkg:pypi/[email protected]. The probe workflow linked below contains a self-contained copy.

python3 -m venv .venv && .venv/bin/pip install six==1.16.0
printf 'six==1.16\n' > requirements.txt
socket-patch scan --mode hosted --yes --json \
  --api-url http://127.0.0.1:$PORT --org test-org --patch-server-url http://127.0.0.1:$PORT
# → "status": "success", exit 0
#   "redirect": {"redirected": 0, "warnings": [{"code": "redirect_requirements_entry_not_found",
#                "detail": "no requirements.txt entry for [email protected]"}]}
cat requirements.txt            # → six==1.16  (unchanged)
python3 -m venv fresh && fresh/bin/pip install -r requirements.txt
fresh/bin/python -c 'import six; print(getattr(six, "SOCKET_PATCHED", 0))'   # → 0 (upstream 1.16.0)

# control: the same project with `six==1.16.0` is rewritten and the fresh install is PATCHED.

This reproduced on every run (local Linux pip 20.3.4 / 23.3.2 / 24.0, plus all CI cells below).

Expected vs actual

  • Expected: CLI_CONTRACT.md (hosted mode) says scan should "rewrite ONLY the patched dependencies' lockfile / registry-config entries to point at the hosted packages". The requirement six==1.16 is the patched dependency: pip's resolver picks exactly six 1.16.0 for it, the release the patch targets. docs/ecosystems.md says only "Version/source ambiguity is refused", and ==1.16 is not ambiguous under PEP 440, because zero-padding makes it equal to 1.16.0 and nothing else. The pin should be rewritten. Failing that, the run should at least fail or say the pin was skipped, rather than claim there is no entry and exit 0.
  • Actual: the rewrite is silently skipped, the run exits 0, and the project installs unpatched bytes.

OS × pip matrix (probe run 36878654662)

OS Python pip ==1.16.0 (control) ==1.16 ==1.16.0.0 Six==01.16.0
ubuntu-latest 3.8 20.3.4, 23.0.1 PATCHED ❌ UNPATCHED ❌ ❌
ubuntu-latest 3.13 24.0, bundled PATCHED ❌ ❌ ❌
macos-latest 3.8 20.3.4, 23.0.1, bundled PATCHED ❌ ❌ ❌
macos-latest 3.13 24.0, 26.2.1 PATCHED ❌ ❌ ❌
windows-latest 3.8 20.3.4, 23.0.1, 21.1.1 (bundled) PATCHED ❌ ❌ ❌
Linux (local) 3.10 / 3.12 20.3.4, 23.3.2, 24.0 PATCHED ❌ ❌ ❌

First bad commit

git bisect with the repro above: ade011e (#238) is good, and 745e8dc "Harden uv lockfile patch preservation and verify every uv release family" (#239) is the first bad commit. The published v4.0.0 (96df6ae) matched requirements lines by name only and rewrites six==1.16.

Suspect code

  • crates/socket-patch-core/src/patch/redirect/requirements.rs:248: RequirementVersion::Exact(version) if version != dep.version => continue is a raw string comparison. A PEP 440 comparison (pad release segments with zeros, strip leading zeros, normalise case and spelling) would match pip.
  • crates/socket-patch-core/src/vendor/pypi_requirements.rs:81: the vendored spec_no_ws == format!("=={version}") has the same raw comparison, which produces the false pypi_requirement_not_pinned refusal.
  • The lock-only path (utils/requirements.rs::exact_pin) carries the spelled version through to the purl (pkg:pypi/[email protected] in the batch request). It may need the same normalisation when no venv is present.

Activity

  1. Mikola Lysenko (mikolalysenko)
    added
    bugSomething isn't workingSomething isn't working
    bughuntFound by a scheduled package-manager bug-hunt agentFound by a scheduled package-manager bug-hunt agent
    pm:pippip / requirements.txtpip / requirements.txt
    agent:triaged
    priority:p1
    on Oct 1, 2026
  2. mikolalysenko commented on Oct 1, 2026

    @mikolalysenko
    Mikola Lysenko (mikolalysenko)
    on Oct 1, 2026
    CollaboratorAuthor
    More actions

    [agent] Triaged as priority:p1 (pip). Confirmed on main 6e7ef74: patch/redirect/requirements.rs compares RequirementVersion::Exact(version) != dep.version as raw strings, and vendor/pypi_requirements.rs compares spec_no_ws == format!("=={version}"), so PEP 440-equivalent pins (==1.16, ==1.16.0.0, ==01.16.0) miss. No duplicate or open fix PR found.


    Generated by Claude Code

  3. mikolalysenko commented on Oct 1, 2026

    @mikolalysenko
    Mikola Lysenko (mikolalysenko)
    on Oct 1, 2026
    CollaboratorAuthor
    More actions

    [agent] Claiming this issue (shared root cause: requirements.txt pin matching compares versions as raw strings instead of under PEP 440). Branch: agent/fix-requirements-pep440-pin-match. Claim-ID: 2026-10-01T15:20:44Z-19ebc3


    Generated by Claude Code

  4. Mikola Lysenko (mikolalysenko)
    added
    agent:claimed
    on Oct 1, 2026
  5. mikolalysenko commented on Oct 1, 2026

    @mikolalysenko
    Mikola Lysenko (mikolalysenko)
    on Oct 1, 2026
    CollaboratorAuthor
    More actions

    [agent] Draft PR: #478


    Generated by Claude Code

  6. Mikola Lysenko (mikolalysenko)
    added 2 commits that reference this issue on Oct 1, 2026

    Match requirements pins like pip does (PEP 440)

    This issue will close once commit eea2f62 is merged into the 'main' branch.
    eea2f62

    Test hosted get over a PEP 440-equivalent pin

    This issue will close once commit bcc5335 is merged into the 'main' branch.
    bcc5335
  7. mikolalysenko commented on Oct 1, 2026

    @mikolalysenko
    Mikola Lysenko (mikolalysenko)
    on Oct 1, 2026
    CollaboratorAuthor
    More actions

    [agent] #478 is ready for review. It fixes the hosted rewrite, the false vendored pypi_requirement_not_pinned refusal, and the same raw comparison in the Hatch rewriter. Follow-up not in #478: on a lock-only checkout with no venv, exact_pin still carries the spelled version into the purl (pkg:pypi/[email protected]). Matching that to the 1.16.0 release needs PyPI's canonical release spelling, so it's left for a separate change. Details are in the PR's "Notes / follow-ups".


    Generated by Claude Code

  8. Mikola Lysenko (mikolalysenko)
    closed this as completedin bf0e0d1on Oct 2, 2026
  9. Mikola Lysenko (mikolalysenko)
    removed
    agent:claimed
    on Oct 2, 2026
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 workingSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentFound by a scheduled package-manager bug-hunt agentpm:pippip / requirements.txtpip / requirements.txtpriority:p1

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.