Repository navigation
Hosted NuGet rewrites packages.lock.json entries at other versions of the patched id #593
Description
Activity
- addedbugSomething isn't workingSomething isn't workingpm:nugetNuGet / dotnetNuGet / dotnetarch-auditFiled by a scheduled architecture audit routine (see the architecture review discussion)Filed by a scheduled architecture audit routine (see the architecture review discussion)
on Oct 2, 2026 mikolalysenko commented
on Oct 2, 2026 CollaboratorAuthorMore actions[agent] Triage: priority:p3 (NuGet). Distinct from #561/#585/#594 (those are
nuget.configreading; this is thepackages.lock.jsonwalk). Note that open PR #597 also editsrewrite_nuget, so this should land after it. No open PR covers it.
Generated by Claude Code
mikolalysenko commented
on Oct 3, 2026 CollaboratorAuthorMore actions[agent] Real-
dotnetconfirmation from the scheduled NuGet / dotnet bug-hunt routine (ledger #320). Main045d7ec, Linux, SDK 8.0.131. Reproduced twice.Fixture:
<TargetFrameworks>net8.0;netstandard2.0</TargetFrameworks>,RestorePackagesWithLockFile, withNewtonsoft.Json13.0.3 fornet8.0and 12.0.3 fornetstandard2.0. Patch for 13.0.3. Wiremock stand-in frome2e_nuget_dotnet_build.rs.One correction to the impact section: in hosted mode, locked mode does not refuse.
mode scanlock diff fresh dotnet restore --locked-modefresh plain dotnet restorehosted exit 0, success,redirected: 1, no warningnetstandard2.0:resolved12.0.3 → 13.0.3,contentHash→ patched hash (requestedstays[12.0.3, ))succeeds. netstandard2.0silently gets 13.0.3, because[12.0.3, )admits itsucceeds, only 13.0.3in the storevendored exit 0, success, no warningonly the net8.0entry is re-pinnedNU1102 Unable to find package Newtonsoft.Json with version (>= 12.0.3) … Found 1 version(s) in socket-patch-<uuid> … Versions from nuget.org were not consideredNU1102 (same) What this means in practice:
- Hosted silently upgrades the other framework's dependency across a major version, and nothing reports it.
- Vendored breaks every restore of the project while still reporting success.
Both outcomes support the shared refusal (
nuget_lock_other_version) that the issue proposes.
Generated by Claude Code
mikolalysenko commented
on Oct 7, 2026 CollaboratorAuthorMore actions[agent] Re-checked on main
05ecc6e. The code moved, and the defect is unchanged.- Hosted
rewrite_nugetstill matches entries by id alone (id.to_lowercase() == id_lower), in every framework, and overwritesresolvedandcontentHashwith no check of the entry's ownresolvedagainst the patched version. - Vendored
locked_atstill filters onnormalize_nuget_version(e.resolved) == version_norm. - Hosted restore is in
upstream/nuget.rs, with the same version-blind walk.
No open PR claims this issue.
Generated by Claude Code
- Hosted
mikolalysenko commented
on Oct 9, 2026 CollaboratorAuthorMore actions[agent] Re-checked on main @
f3c6313(architecture audit, ecosystems and formats). This still holds, but the code moved. Hostedrewrite_nugetis now atredirect/mod.rs#L5599. Its lock walk at#L5716-L5760still matches on id only and overwritesresolved/contentHashin every framework, without comparing the entry'sresolvedto the patched version.
Generated by Claude Code
- addedv5-blockerMust resolve before v5: public interface/migration or ordinary patch-install-undo failure.Must resolve before v5: public interface/migration or ordinary patch-install-undo failure.compatibilityPublic CLI/JSON, saved state, upgrades, or package-manager compatibility.Public CLI/JSON, saved state, upgrades, or package-manager compatibility.and removed
on Oct 9, 2026 mikolalysenko commented
on Oct 9, 2026 CollaboratorAuthorMore actionsv5 release blocker (P1). A hosted NuGet rewrite must match both package id and version. Rewriting other versions corrupts an otherwise ordinary multi-target lockfile.
This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.
mikolalysenko commented
on Oct 9, 2026 CollaboratorAuthorMore actions[agent] Claiming for v5 blocker burn-down (shared root cause: hosted rewrite_nuget walks packages.lock.json with its own bare serde_json + id-only matcher instead of the shared BOM-tolerant, version-filtered lock reader). Branch: agent/v5-nuget-lock-reader. Claim-ID: 2026-10-09T16:44Z-47bc41
- added a commit that references this issue
on Oct 9, 2026
[agent] Filed by the scheduled architecture audit routine (ecosystems and formats). Register: E50.
Kind: bug. Source: new finding; register E50.
Problem
packages.lock.jsonis walked by four code paths, and hosted and vendored disagree about which entries belong to the patched package.locked_atonly takes entries whoseresolvednormalizes to the patched version. Its own test says so: "A different resolved version is not our package → nothing to pin".``rewrite_nugetrewrites every entry of the id, in every target framework, whatever itsresolvedis. It setsresolvedto the patched version andcontentHashto the patched hash:upstream::nuget::restore`` is version-blind too: it sets every entry of the id to the restored version.nuget_lock_entries(the vendored walker without the version filter).Repro (unit test, run twice on main
203e092). A multi-targeting lock wherenet6.0resolvesNewtonsoft.Json12.0.3 andnet8.0resolves 13.0.3, with a hosted patch for 13.0.3:rewrite_registry_redirect(hosted):net6.0becomes{"requested":"[12.0.3, )","resolved":"13.0.3","contentHash":"PATCHED=="}, with 2redirect_nuget_lockedits.edit_lock(vendored), same lock and patch:net6.0stays{"resolved":"12.0.3","contentHash":"OLD12=="}.So hosted silently upgrades the
net6.0dependency to the patched version's bytes in the lock. Restore can't undo it, because it rebuilds every entry at the restored version and the 12.0.3 pin is gone.Symptoms
None filed yet. Related lock-selection bugs in the same rewriters: #514 (per-project
packages.<project>.lock.json) and #353 (member-project locks).Impact
Multi-targeting projects (
<TargetFrameworks>with per-frameworkPackageReferenceversions, or a transitive pin that differs per framework) get a lock that doesn't match their project files. In locked mode NuGet then refuses restore (NU1004), and in unlocked mode it silently re-resolves. The vendored path avoids the lock damage, but it routes the id to a feed that only serves the patched version, so that framework can't restore either. Neither mode tells the user. Size: the four walkers are about 120 production lines.Proposed change
NugetLockEntry/nuget_lock_entries/locked_atandnormalize_nuget_versionfromvendor/nuget_feed.rsintoformats::nuget(a lock section next to the config reader), with a mutable variant that yields(framework, id, &mut entry).rewrite_nugetandupstream::nuget::restoreuse it, so they pin only the entries at the patched version. Delete both hand-rolled walks.nuget_lock_other_version, documented inCLI_CONTRACT.md). The id-level<packageSourceMapping>would route that framework to a feed that doesn't serve its version.Size and scope
formats/nuget/,patch/redirect/mod.rs(the NuGet lock block),patch/redirect/upstream/nuget.rs,vendor/nuget_feed.rs,vex/discover/nuget.rs(import path only). About +80 / −90 production lines. Out of scope: the config reader and splice anchors (E11) and multi-version support itself.Acceptance criteria
net6.0untouched and refuses with the shared code, and so does vendored.packages.lock.jsonwalker; noget("dependencies")walk of a NuGet lock is left inredirect/.cargo test -p socket-patch-core --lib nuget,e2e_nuget*).Dependencies
Independent of #561 and E11, although all three touch
rewrite_nuget; land them in one sequence to avoid conflicts.