Package Source Mapping in NuGet — hardening mixed public/private feeds
TL;DR: Add a small block to nuget.config. You'll eliminate a big class of supply-chain issues—especially if you use Azure Artifacts alongside nuget.org.
Why it matters
From real-world use on multi-repo .NET solutions:
- Deterministic restores. Without mapping, NuGet can resolve the same ID from different sources on different machines/agents. Mapping removes the ambiguity.
- Supply-chain hardening. Prevents public packages from impersonating your private ones (classic dependency confusion) and reduces typosquatting blast radius.
- Team clarity. New joiners and build agents don't need tribal knowledge—policy lives in
nuget.config.
See the official docs and .NET blog for background and examples. [1][2]
Minimal working example (nuget.config)
Scenario: OSS packages from nuget.org; internal packages from Azure DevOps Artifacts.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="AzDo-Company" value="https://pkgs.dev.azure.com/YourOrg/_packaging/YourFeed/nuget/v3/index.json" />
</packageSources>
<packageSourceMapping>
<!-- Private packages allowed ONLY from AzDo -->
<packageSource key="AzDo-Company">
<package pattern="Company.*" />
<package pattern="YourTeam.*" />
<package pattern="Private.ComponentA" />
</packageSource>
<!-- Everything else must come from nuget.org -->
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
How it works: package IDs matching Company.*/YourTeam.*/Private.ComponentA can only restore from your AzDo feed; all other packages must come from nuget.org.
Quick start (my pragmatic flow)
- Commit the repo-root
nuget.configwith a<packageSourceMapping>block (above). - Turn on Central Package Management (CPM) with a
Directory.Packages.propsat repo root:<Project> <PropertyGroup> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> <CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled> <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile> </PropertyGroup> </Project> dotnet restoreonce locally to generatepackages.lock.json, commit it.- In CI (GitHub Actions / AzDO), always restore with the repo
nuget.config:dotnet restore --locked-mode --configfile ./nuget.config - Tighten patterns over time. Start with
Company.*, then move to explicit IDs for stricter control.
Azure DevOps specifics (things that bite)
- Upstreams/extra sources. Some pipeline tasks add sources implicitly. Force the repo config with
--configfile ./nuget.config. [3][4][5] - Service connections/PAT auth. If your AzDo feed is private, ensure the agent has a token or uses the feed-aware NuGet auth task; then let mapping govern where each package may come from. [3][5]
- Nested repos/nuget.config hierarchy. If you have multiple
nuget.configfiles (root + submodule), errors can arise; keep mapping in the top-level config where possible. [6]
What can go wrong without mapping?
- Dependency confusion. An attacker publishes
Company.Coreto public nuget.org at a higher version; your build pulls the attacker's package. Mapping blocks this by disallowingCompany.*on public sources. [7][8] - Typosquatting.
Seriloggresolves silently from public feeds. Mapping won't fix fat-fingers by itself, but it limits where typos can resolve from—pair with SCA/advisories. [3] - Non-deterministic restores. Different devs/agents + different source order = different origins. Mapping makes the origin explicit. [1]
Troubleshooting tips
- Restore fails after enabling mapping. A transitive dependency isn't permitted from any source. Add an allow pattern for it (usually to
nuget.org). - VS "Package source mapping is off". That means multiple sources but no mapping—configure it in
nuget.config. [9][10] - Audit the effective mapping. Run a diagnostic restore and search the log for "Mapped package":
dotnet restore -v diag | findstr /i "Mapped package" # Windows # or dotnet restore -v diag | grep -i "Mapped package" # macOS/Linux
Wrap-up
Package Source Mapping is a tiny, high-leverage config: it locks down where packages are allowed to come from, keeps restores reproducible, and materially reduces supply-chain risk when using Azure Artifacts plus nuget.org. Add it once, enforce it in CI, and keep the allow-list tight.
Sources & further reading
- NuGet Docs — Package Source Mapping: https://learn.microsoft.com/nuget/consume-packages/package-source-mapping
- .NET Blog — Introducing Package Source Mapping: https://devblogs.microsoft.com/dotnet/introducing-package-source-mapping/
- NuGet Docs — Security best practices (supply-chain): https://learn.microsoft.com/nuget/concepts/security-best-practices
- Azure DevOps — Get started with NuGet & Azure Artifacts: https://learn.microsoft.com/azure/devops/artifacts/get-started-nuget?view=azure-devops
- Azure DevOps — Restore NuGet packages in Pipelines: https://learn.microsoft.com/azure/devops/pipelines/packages/nuget-restore?view=azure-devops
- GitHub Discussion — packageSourceMapping + nested configs issue: https://github.com/NuGet/Home/discussions/11528
- NuGet/Home — Preventing dependency confusion: https://github.com/NuGet/Home/issues/10566
- Alex Birsan — Dependency Confusion write-up: https://medium.com/@alex.birsan/dependency-confusion-how-i-hacked-into-apple-microsoft-and-dozens-of-other-companies-4a5d60fec610
- StackOverflow — "Package source mapping is off" in VS 2022: https://stackoverflow.com/questions/77610890/how-to-fix-package-source-mapping-is-off-in-visual-studio-2022
- NuGet Blog Tag — Package Source Mapping + tools: https://devblogs.microsoft.com/dotnet/tag/package-source-mapping/
- Meziantou — Faster & safer restore with mapping + lock files: https://www.meziantou.net/faster-and-safer-nuget-restore-using-source-mapping-and-lock-files.htm
Have questions about NuGet package source mapping or supply-chain security? Drop a comment below!