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
uv vendored → hosted takeover strands a package that vendored mode pinned to a different version than uv.lock: the wet run reverts to the unpatched release (exit 1), while --dry-run previews a clean takeover #723
[agent] Found by the scheduled uv bug-hunt routine (ledger #310).
Summary
On a uv project, vendored mode pins the patched package to the patch's version even when uv.lock resolved another one (pypi_uv.rs does this on purpose: the unit test override_wiring_matches_fixture_byte_identically covers "the lock's 1.17.0 → 1.16.0 version pin-down"). For example, python-dateutil pulls in six 1.17.0, the manifest patch is for six@1.16.0, and vendor writes override-dependencies = ["six==1.16.0"] plus a path source, and rewrites the lock entry to version = "1.16.0". A direct six>=1.15 gets the same treatment, with no warning.
The vendored → hosted takeover (scan --mode hosted over that project, from #503) doesn't account for this:
Wet run: it reverts the vendored wiring first. That deletes the committed wheel and the ledger entry, and restores six 1.17.0 in uv.lock. Only then does it ask the uv rewriter to pin six@1.16.0, which no longer has a lock entry: redirect_uv_entry_not_found, redirected: 0, partial_failure, redirect_takeover_unpatched, exit 1. A project that installed patched six before the command now installs unpatchedsix 1.17.0 from PyPI.
--dry-run: reports status: success, redirected: 1 and only redirect_would_revert_vendored. Every previewed takeover is counted as confirmed (hosted.rs:1018, confirmed.extend(dry_run_takeover)), so the strand is never predicted.
This is the uv counterpart of #699 (requirements.txt). The cause is different: the lock entry the hosted rewriter needs disappears with the revert. Open PR #708 adds a takeover preflight only for requirements wiring (preflight_requirements_takeover), and I verified this case still strands on its head 5f291a5.
Impact
A user who previews with --dry-run, sees a clean takeover, and runs it loses the patch. uv sync --locked succeeds and installs the vulnerable upstream release, with the vendored wheel already deleted.
It's reachable whenever vendored mode was run with a manifest entry whose version differs from the lock's. For example, a committed .socket/manifest.json from agent mode or get records six@1.16.0, and uv lock --upgrade later moved the transitive six to 1.17.0. Hosted discovery then finds six@1.16.0 in the vendored lock (Socket's own references stay discoverable), so the API offers the 1.16.0 patch and the takeover runs.
The printed remedy ("re-run scan --mode hosted") can't work: hosted mode never pins a version the lock doesn't resolve. Only scan --mode vendored recovers.
Repro
I used the mode_migration_pypi.rs harness: stage_manifest (six@1.16.0), the prebuilt fixture server for vendor, and mount_hosted_api for the hosted scan. Real uv was used for lock and install.
The direct variant dependencies = ["six>=1.15"] (lock: 1.17.0) behaves identically.
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". The dry-run preview must report the wet run's outcome (dry_run_predicts_drifted_takeover_refusal pins this for drifted wiring). When the reverted lock won't resolve the patch's version, the takeover should be refused before the revert, keeping the vendored patch, and the dry run should predict that refusal. A uv-side twin of the npm takeover_refusal gates at hosted.rs:1725, which today returns None for every non-npm purl, would cover it.
Actual: the wet run reverts first and strands the package unpatched (exit 1). The dry run previews success with redirected: 1.
The decision is made on lock text, so it doesn't depend on the OS, and I ran no probe. First bad: #503 (0ac5b91a), which enabled the PyPI takeover. Before it the takeover was refused (#328), and the vendored patch survived.
[agent] Found by the scheduled uv bug-hunt routine (ledger #310).
Summary
On a uv project, vendored mode pins the patched package to the patch's version even when
uv.lockresolved another one (pypi_uv.rsdoes this on purpose: the unit testoverride_wiring_matches_fixture_byte_identicallycovers "the lock's 1.17.0 → 1.16.0 version pin-down"). For example,python-dateutilpulls insix 1.17.0, the manifest patch is forsix@1.16.0, andvendorwritesoverride-dependencies = ["six==1.16.0"]plus a path source, and rewrites the lock entry toversion = "1.16.0". A directsix>=1.15gets the same treatment, with no warning.The vendored → hosted takeover (
scan --mode hostedover that project, from #503) doesn't account for this:six 1.17.0inuv.lock. Only then does it ask the uv rewriter to pinsix@1.16.0, which no longer has a lock entry:redirect_uv_entry_not_found,redirected: 0,partial_failure,redirect_takeover_unpatched, exit 1. A project that installed patched six before the command now installs unpatchedsix 1.17.0from PyPI.--dry-run: reportsstatus: success,redirected: 1and onlyredirect_would_revert_vendored. Every previewed takeover is counted as confirmed (hosted.rs:1018,confirmed.extend(dry_run_takeover)), so the strand is never predicted.This is the uv counterpart of #699 (requirements.txt). The cause is different: the lock entry the hosted rewriter needs disappears with the revert. Open PR #708 adds a takeover preflight only for requirements wiring (
preflight_requirements_takeover), and I verified this case still strands on its head5f291a5.Impact
--dry-run, sees a clean takeover, and runs it loses the patch.uv sync --lockedsucceeds and installs the vulnerable upstream release, with the vendored wheel already deleted..socket/manifest.jsonfrom agent mode orgetrecordssix@1.16.0, anduv lock --upgradelater moved the transitivesixto 1.17.0. Hosted discovery then findssix@1.16.0in the vendored lock (Socket's own references stay discoverable), so the API offers the 1.16.0 patch and the takeover runs.scan --mode hosted") can't work: hosted mode never pins a version the lock doesn't resolve. Onlyscan --mode vendoredrecovers.Repro
I used the
mode_migration_pypi.rsharness:stage_manifest(six@1.16.0), the prebuilt fixture server forvendor, andmount_hosted_apifor the hosted scan. Real uv was used for lock and install.The direct variant
dependencies = ["six>=1.15"](lock: 1.17.0) behaves identically.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". The dry-run preview must report the wet run's outcome (dry_run_predicts_drifted_takeover_refusalpins this for drifted wiring). When the reverted lock won't resolve the patch's version, the takeover should be refused before the revert, keeping the vendored patch, and the dry run should predict that refusal. A uv-side twin of the npmtakeover_refusalgates athosted.rs:1725, which today returnsNonefor every non-npm purl, would cover it.redirected: 1.Matrix (Linux, main
045d7ec)six>=1.15uv sync --lockedafter the wet runredirected: 1redirected: 1redirected: 15f291a5redirected: 1redirected: 1, patched)The decision is made on lock text, so it doesn't depend on the OS, and I ran no probe. First bad: #503 (
0ac5b91a), which enabled the PyPI takeover. Before it the takeover was refused (#328), and the vendored patch survived.Suspect code
crates/socket-patch-cli/src/commands/scan/hosted.rs:1018:confirmed.extend(dry_run_takeover).crates/socket-patch-cli/src/commands/scan/hosted.rs:1725:takeover_refusalgates npm only. PyPI has no "will the hosted rewriter reach this after the revert" check, and Fix PyPI vendored→hosted takeover stranding unreachable pins (#699) #708 adds one for requirements only.crates/socket-patch-core/src/vendor/pypi_uv.rs:1150: the lock entry'sversionis rewritten to the patch version (the pin-down that the revert undoes).crates/socket-patch-core/src/patch/redirect/mod.rs:4643:redirect_uv_entry_not_found, which the takeover runs into after reverting.Related: #699 / PR #708 (requirements.txt shape), #568 (drift-kept ledger), #369 (the yarn berry reverse direction, closed).