You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Vendored → hosted takeover strands a requirements.txt pin that lives in a -r include or a vendored "(transitive)" line: the wet run reverts it to the unpatched release, while --dry-run previews a clean takeover #699
[agent] Found by the scheduled pip / requirements.txt bug-hunt routine (ledger #309).
Summary
Vendored mode follows -r includes, and it appends a # socket-patch vendor: six==X (transitive) line to the root requirements.txt for a package pinned only in a file outside the include tree. Hosted mode rewrites only the root requirements.txt and refuses anything else with redirect_requirements_entry_not_found (documented). The vendored → hosted takeover (scan --mode hosted over a vendored project, from #503) doesn't take that gap into account:
Wet run: it first reverts the vendored wiring. That deletes the committed wheel, drops the ledger entry, and puts back six==1.16.0 in base.txt or removes the transitive line. Only then does it ask the requirements rewriter to pin the hosted URL, which can't reach that entry. Result: redirected: 0, partial_failure, exit 1, redirect_takeover_unpatched. A project that was patched before the command now installs the unpatched release.
--dry-run: reports status: success, redirected: 1 and only redirect_would_revert_vendored. Every takeover preview counts as confirmed (confirmed.extend(dry_run_takeover)), so the stranding is never predicted.
The printed remedy ("fix the reported cause and re-run scan --mode hosted") can't work, because hosted mode never rewrites -r includes or adds lines. Only scan --mode vendored recovers.
Impact
A user who previews with --dry-run, sees a clean takeover, and then runs it loses their patch. The vendored artifact is deleted, and pip install -r requirements.txt installs the vulnerable upstream release (verified with real pip below).
This affects every vendored requirements.txt project whose patched pin sits in a -r include (a very common requirements.txt → -r base.txt layout), and every project where vendored mode added a (transitive) line.
Transitive variant: requirements.txt = idna==3.7, requirements-dev.txt = six==1.16.0. vendor appends ./.socket/vendor/pypi/<uuid>/six-1.16.0-py3-none-any.whl # socket-patch vendor: six==1.16.0 (transitive) to requirements.txt. The dry run then says redirected: 1, and the wet run removes the line, exits 1 with redirect_takeover_unpatched, and leaves six unpatched.
Expected vs actual
Expected (CLI_CONTRACT §scan --mode hosted / the vendored_takeover doc in hosted.rs): "A takeover must leave the project FULLY hosted … A purl whose vendored state cannot be cleanly reverted … is REFUSED — skipped with an actionable error — never half-migrated". Also, --dry-run previews must report the wet run's outcome (hosted.rs ~1012: "the preview's redirected count must report that outcome"; dry_run_predicts_drifted_takeover_refusal pins this for drifted wiring). When the hosted rewriter can't pin the entry because it isn't in the root requirements.txt, the takeover should be refused before the revert, keeping the vendored patch, and the dry run should predict that refusal.
Actual: the wet run reverts first and strands the package unpatched (exit 1). The dry run previews success with redirected: 1.
Matrix
Main 045d7ec, Linux. The decision is made on requirements text, so it doesn't depend on the OS or the pip version, and there was no probe run. Real pip confirms the install outcome.
First bad: 0ac5b91a (#503, which enabled the PyPI vendored → hosted takeover). Before it, the takeover was refused outright (#328), so the vendored patch survived.
Suspect code
crates/socket-patch-cli/src/commands/scan/hosted.rs:1018: confirmed.extend(dry_run_takeover) counts every previewed takeover as redirected.
crates/socket-patch-cli/src/commands/scan/hosted.rs:1567 (vendored_takeover, dry-run arm ~1780–1810, plus the wet arm): gates only on revertability, not on whether the hosted rewriter can reach the entry. For requirements.txt, that means the vendored line must be in the root file and must not be a vendor-added (transitive) line.
crates/socket-patch-core/src/patch/redirect/requirements.rs:355: the root-only redirect_requirements_entry_not_found refusal that the takeover runs into after reverting.
Related but distinct: #659 (npm v1, the opposite direction), #410 (sole-pin refusal), #412 (lock-only include discovery).
[agent] Found by the scheduled pip / requirements.txt bug-hunt routine (ledger #309).
Summary
Vendored mode follows
-rincludes, and it appends a# socket-patch vendor: six==X (transitive)line to the rootrequirements.txtfor a package pinned only in a file outside the include tree. Hosted mode rewrites only the rootrequirements.txtand refuses anything else withredirect_requirements_entry_not_found(documented). The vendored → hosted takeover (scan --mode hostedover a vendored project, from #503) doesn't take that gap into account:six==1.16.0inbase.txtor removes the transitive line. Only then does it ask the requirements rewriter to pin the hosted URL, which can't reach that entry. Result:redirected: 0,partial_failure, exit 1,redirect_takeover_unpatched. A project that was patched before the command now installs the unpatched release.--dry-run: reportsstatus: success,redirected: 1and onlyredirect_would_revert_vendored. Every takeover preview counts as confirmed (confirmed.extend(dry_run_takeover)), so the stranding is never predicted.scan --mode hosted") can't work, because hosted mode never rewrites-rincludes or adds lines. Onlyscan --mode vendoredrecovers.Impact
--dry-run, sees a clean takeover, and then runs it loses their patch. The vendored artifact is deleted, andpip install -r requirements.txtinstalls the vulnerable upstream release (verified with real pip below).-rinclude (a very commonrequirements.txt→-r base.txtlayout), and every project where vendored mode added a(transitive)line.Repro
Uses the
mode_migration_pypi.rsharness:stage_manifest+vendoragainst the prebuilt fixture server, thenmount_hosted_api+hosted_scan_args(uri).Transitive variant:
requirements.txt=idna==3.7,requirements-dev.txt=six==1.16.0.vendorappends./.socket/vendor/pypi/<uuid>/six-1.16.0-py3-none-any.whl # socket-patch vendor: six==1.16.0 (transitive)torequirements.txt. The dry run then saysredirected: 1, and the wet run removes the line, exits 1 withredirect_takeover_unpatched, and leavessixunpatched.Expected vs actual
scan --mode hosted/ thevendored_takeoverdoc inhosted.rs): "A takeover must leave the project FULLY hosted … A purl whose vendored state cannot be cleanly reverted … is REFUSED — skipped with an actionable error — never half-migrated". Also,--dry-runpreviews must report the wet run's outcome (hosted.rs~1012: "the preview'sredirectedcount must report that outcome";dry_run_predicts_drifted_takeover_refusalpins this for drifted wiring). When the hosted rewriter can't pin the entry because it isn't in the rootrequirements.txt, the takeover should be refused before the revert, keeping the vendored patch, and the dry run should predict that refusal.redirected: 1.Matrix
Main
045d7ec, Linux. The decision is made on requirements text, so it doesn't depend on the OS or the pip version, and there was no probe run. Real pip confirms the install outcome.pip install -rafterwards (pip 26.2.1 / py3.13, pip 20.3.4 / py3.8)-r base.txtredirected: 1requirements-dev.txt(vendored(transitive)line)redirected: 1requirements.txt(control)redirected: 1requirements_vendored_to_hostedtest)First bad:
0ac5b91a(#503, which enabled the PyPI vendored → hosted takeover). Before it, the takeover was refused outright (#328), so the vendored patch survived.Suspect code
crates/socket-patch-cli/src/commands/scan/hosted.rs:1018:confirmed.extend(dry_run_takeover)counts every previewed takeover as redirected.crates/socket-patch-cli/src/commands/scan/hosted.rs:1567(vendored_takeover, dry-run arm ~1780–1810, plus the wet arm): gates only on revertability, not on whether the hosted rewriter can reach the entry. For requirements.txt, that means the vendored line must be in the root file and must not be a vendor-added(transitive)line.crates/socket-patch-core/src/patch/redirect/requirements.rs:355: the root-onlyredirect_requirements_entry_not_foundrefusal that the takeover runs into after reverting.Related but distinct: #659 (npm v1, the opposite direction), #410 (sole-pin refusal), #412 (lock-only include discovery).