Skip to content

Agent-mode NuGet apply patches ~/.nuget/packages instead of the project's configured globalPackagesFolder / RestorePackagesPath, reports success, and VEX attests not_affected #397

Description

[agent] Found by the scheduled NuGet / dotnet bug-hunt routine (ledger #320).

Summary

When a project moves its NuGet global packages folder, either with <add key="globalPackagesFolder" value=".nuget/packages" /> in nuget.config or with <RestorePackagesPath> in Directory.Build.props / the csproj, agent-mode apply patches the wrong copy:

  • Default cache is warm (any dev machine or CI image that has restored the same package id/version for another project): apply patches the user-wide ~/.nuget/packages/<id>/<ver>/ (%USERPROFILE%\.nuget\packages on Windows) and reports 1 of 1 targeted patch applied, exit 0. The project's own folder, the only one dotnet build resolves from (obj/project.assets.json → packageFolders), stays unpatched. socket-patch vex then emits not_affected / inline_mitigations_already_exist for the unpatched product. As a side effect, every other project on the machine that uses the default folder gets its bytes modified.
  • Default cache is cold and the project sits two directory levels below the scan root (the common App.sln + src/App/App.csproj layout): apply fails with "matched no installed package" (exit 1), because discover_paths_from_assets reads obj/project.assets.json only at the root and one level down.

Root cause (suspected)

NuGetCrawler::get_nuget_package_paths (crates/socket-patch-core/src/crawlers/nuget_crawler.rs:74-86) lists source roots in the order <cwd>/packages, then nuget_home() (default ~/.nuget/packages, only NUGET_PACKAGES honoured), then the packageFolders read from obj/project.assets.json. The dispatcher keeps only the first match per PURL (merge_first_wins, crates/socket-patch-cli/src/ecosystem_dispatch.rs:147, deliberate for NuGet). So whenever the default folder also holds the package, the project's real, configured folder is never patched. Separately, discover_paths_from_assets (nuget_crawler.rs:483) searches only one level deep, so a src/<Project>/obj/project.assets.json is never seen. Neither globalPackagesFolder nor RestorePackagesPath is read anywhere.

Impact

A silent false "patched", plus a false VEX not_affected attestation, for any repo that pins a repo-local packages folder. That's a common hermetic-CI pattern. Agent mode also mutates a shared user-wide cache the project doesn't use.

Repro (Linux, dotnet SDK 8.0.131, main f6b7fb9)

SP=/path/to/socket-patch
# 1. warm the default cache with the same package, as any other project would
mkdir warm && cd warm && cat > W.csproj <<'X'
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>net8.0</TargetFramework></PropertyGroup><ItemGroup><PackageReference Include="Newtonsoft.Json" Version="13.0.3" /></ItemGroup></Project>
X
dotnet restore && cd ..
# 2. project that relocates its packages folder
mkdir -p proj/src/App && cd proj
cat > nuget.config <<'X'
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <config><add key="globalPackagesFolder" value=".nuget/packages" /></config>
</configuration>
X
cat > src/App/App.csproj <<'X'
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net8.0</TargetFramework></PropertyGroup><ItemGroup><PackageReference Include="Newtonsoft.Json" Version="13.0.3" /></ItemGroup></Project>
X
echo 'System.Console.WriteLine(1);' > src/App/Program.cs
dotnet new sln -n App && dotnet sln add src/App/App.csproj && dotnet restore
# 3. stage a manifest patching LICENSE.md (sha256 git-blob hashes; blob in .socket/blobs/<afterHash>),
#    plus "setup": {"manual": ["nuget"]} so vex runs
$SP apply --offline         # -> "1 of 1 targeted patch applied", exit 0
grep -c MARKER .nuget/packages/newtonsoft.json/13.0.3/LICENSE.md     # 0  (the copy the build uses)
grep -c MARKER ~/.nuget/packages/newtonsoft.json/13.0.3/LICENSE.md   # 1  (wrong copy)
$SP vex --offline --product pkg:nuget/[email protected]   # status not_affected

RestorePackagesPath behaves the same: Directory.Build.props with <RestorePackagesPath>$(MSBuildThisFileDirectory)pkgs</RestorePackagesPath> and a root-level csproj. NuGetPackageRoot = <repo>/pkgs, yet apply patches ~/.nuget/packages.

With a cold default cache and the src/App layout, apply exits 1 with The targeted manifest patch matched no installed package. With the project one level down (App/App.csproj) and a cold default cache, it correctly patches .nuget/packages.

Expected vs actual

  • Expected: agent apply patches the copy NuGet actually resolves for the project, which is the packageFolders recorded in obj/project.assets.json / the configured globalPackagesFolder / RestorePackagesPath. Failing that, it refuses. README's apply section and the VEX contract (a statement only for a patch that is actually applied) both require this. docs/ecosystems.md documents NuGet agent mode as in-place patching of the installed package, with no caveat about relocated package folders.
  • Actual: it patches an unrelated user-wide cache, reports success, and VEX attests not_affected.

OS × SDK matrix (probe run https://github.com/SocketDev/socket-patch/actions/runs/36791969674)

OS SDK root-level project src/App project
Linux (local) 8.0.131 wrong copy patched, rc 0 wrong copy patched, rc 0 (cold default cache: not found, rc 1)
Linux 6.0.428 wrong copy patched, VEX not_affected same
Linux 9.0.318 wrong copy patched, VEX not_affected same
Linux 10.0.401 wrong copy patched, VEX not_affected same
macOS 8.0.425 wrong copy patched, VEX not_affected same
macOS 9.0.318 wrong copy patched, VEX not_affected same
Windows 8.0.425 wrong copy patched, VEX not_affected same
Windows 9.0.318 wrong copy patched, VEX not_affected same

Control: an App/ project one level deep with a cold default cache → project copy patched (pass).

First bad version

Not a regression: v4.0.0 (GitHub release binary) behaves identically.

Related but distinct: #338 (the same "wrong copy, success, VEX not_affected" shape for cargo vendor dirs), #352 (the vendored/hosted warm-cache shadowing).

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:nugetNuGet / dotnetpriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions