Repository navigation
Vendored NuGet doesn't recognise a close tag with whitespace (</packageSources >, </packageSourceMapping >), so it appends a second section that NuGet ignores and every restore fails NU1100 / NU1403 while scan reports success and VEX attests #685
Description
[agent] Found by the scheduled NuGet / dotnet bug-hunt routine (ledger #320).
Summary
In vendored mode, the nuget.config writer (build_config_edit) and its key reader (parse_config_source_keys) look for section ends with literal find("</packageSources>") / contains("</packageSourceMapping>"). XML allows whitespace before the > of an end tag, and NuGet parses </packageSources > and </packageSourceMapping > fine. When the user's config uses either spelling, vendored decides the section doesn't exist and appends a second <packageSources> or <packageSourceMapping> element before </configuration>. NuGet reads only the first one, so:
</packageSources >: the Socket source sits in the ignored second section. The mapping routesNewtonsoft.Jsonexclusively to a source that doesn't exist, so every restore fails NU1100.</packageSourceMapping >: the Socket mapping sits in the ignored second section. The id resolves from nuget.org, so with the re-pinned lock every restore (locked or plain) fails NU1403. Without a lock the unpatched package would install silently.
In both cases scan --mode vendored exits 0 with status: success and no warning, and the in-run --vex attests not_affected / inline_mitigations_already_exist.
Hosted mode already tolerates both spellings (patch/redirect/mod.rs builds </{section}\s*> and </configuration\s*> regexes, and the open tag <packageSources >), and its restores pass on the same fixtures. Only the vendored twin is affected.
Impact
A config that dotnet accepts is turned into one that can't restore, and every signal socket-patch gives says it worked: exit 0, success, and a VEX not_affected for a patch that never installs. The spelling is uncommon (hand-edited or tool-generated configs), so the reach is narrow, but the failure is silent at scan time.
Repro
This uses the wiremock stand-in from crates/socket-patch-cli/tests/e2e_nuget_dotnet_build.rs: fixture restore of Newtonsoft.Json 13.0.3, then scan --mode vendored --vendor-source service. Only nuget.config differs from the suite's REGISTRY_CONFIG:
<!-- A -->
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources >
</configuration><!-- B -->
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping >
</configuration>dotnet restore --locked-mode # baseline: Restored (both A and B)
socket-patch scan --mode vendored --vendor-source service --json --yes \
--api-url $URI --org test-org --api-token fake --vex vex.json --vex-product pkg:nuget/app@1.0.0
# exit 0, status success, no warnings; vex.json: not_affected / inline_mitigations_already_exist
rm -rf obj; NUGET_PACKAGES=$(mktemp -d) dotnet restore --locked-mode
# A: error NU1100: Unable to resolve 'Newtonsoft.Json (>= 13.0.3)' for 'net8.0'. PackageSourceMapping is enabled, the following source(s) were not considered: nuget.org.
# B: error NU1403: Package content hash validation failed for Newtonsoft.Json.13.0.3.Resulting config for A (the Socket source lands in a second, ignored section):
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources >
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="socket-patch-<uuid>" value=".socket/vendor/nuget/<uuid>" />
</packageSources>
<packageSourceMapping> … nuget.org → * ; socket-patch-<uuid> → Newtonsoft.Json … </packageSourceMapping>For B, a second <packageSourceMapping> is appended after the user's </packageSourceMapping >.
vendor --revert restores both configs byte-for-byte, so the unwind is fine.
Expected vs actual
- Expected: per CLI_CONTRACT.md (vendored table, nuget row), vendored adds the
nuget.configsource plus thepackageSourceMappingfor the id, verified bydotnet restore --locked-modeon a cold cache. The Socket entries should land in the user's existing sections, as hosted mode does for the same files. If the config can't be edited, it should be refused rather than reported assuccess. - Actual: a duplicate section NuGet ignores, exit 0
success, VEX attests, and every restore fails (NU1100 / NU1403).
Matrix
| OS | SDK | binary | A </packageSources > |
B </packageSourceMapping > |
hosted (A and B) |
|---|---|---|---|---|---|
| Linux | 8.0.131 | main 045d7ec |
fail NU1100 (2/2) | fail NU1403 (2/2) | pass |
| Linux | 8.0.131 | v4.0.0 (npm) | fail NU1100 | fail NU1403 | n/a |
Not a regression; v4.0.0 behaves the same. macOS and Windows weren't probed (the writer is pure string handling, with no OS dependence).
Suspect code
crates/socket-patch-core/src/vendor/nuget_feed.rs:936:creating_mapping = !visible.contains("</packageSourceMapping>")crates/socket-patch-core/src/vendor/nuget_feed.rs:974:visible.find("</packageSources>")falls through to the "create the section" branchcrates/socket-patch-core/src/vendor/nuget_feed.rs:987: same literal for the mapping anchorcrates/socket-patch-core/src/vendor/nuget_feed.rs:1069:parse_config_source_keysreturns no keys, so nuget.org gets re-seeded as a duplicate<add>
The tokenizer refactor proposed in #594 would subsume this. This issue records the user-visible symptom, which #594 doesn't list.
No probe branch was used. Stale bughunt/nuget/* branches can't be deleted from the sandbox, so new probe branches are on hold; see ledger #320.
Activity
mikolalysenko commentedon Oct 3, 2026
[agent] Triage: p3 (NuGet). The suspect lines match main
045d7ec. Incrates/socket-patch-core/src/vendor/nuget_feed.rs,build_config_editandparse_config_source_keysmatch the literal</packageSources>/</packageSourceMapping>. Hosted mode uses</{section}\s*>regexes, so it isn't affected. Not a duplicate: #561 and #585 are hosted mode reading commented-out sections.Shares root cause with #594: the vendored
nuget.configreader/writer does its own substring tag matching instead of going throughformats::nuget. Will be fixed together.Generated by Claude Code
mikolalysenko commentedon Oct 9, 2026
[agent] Claiming this issue for the architecture refactor routine together with #594: the vendored
nuget.configwriter will take its section anchors fromformats::nuget::parse_config, which already accepts</packageSources >and</packageSourceMapping >. Branch: arch-refactor/594-vendored-nuget-config. Claim-ID: 2026-10-09T14:56:13Z-ead28cGenerated by Claude Code
mikolalysenko commentedon Oct 9, 2026
[agent] Draft PR: #1288.
Generated by Claude Code
Wire vendored nuget.config via formats::nuget