Introducing Package Source Mapping

Nikolche

We’re happy to announce the first preview release of Package Source Mapping with Visual Studio 2022 preview 4! Package Source Mapping gives you fine-grained control of where your packages come from by mapping every package in your solution to a target package source.

Safeguarding your software supply chain is crucial if you use a mix of public and private package sources. Earlier this year, we published a set of best security practices to minimize risk from vulnerable packages and supply chain compromises like dependency confusion. Package Source Mapping is another powerful feature to help you fortify your supply chain against attacks. Use Source Mapping to centrally declare which source each package in your solution should restore from in your nuget.config file.

Using Package Source Mapping

This feature is compatible with the following tools:

Older tooling will ignore the Package Source Mapping configuration. To use this feature, ensure all your build environments use compatible tooling versions.

Package Source Mappings will apply to all project types – including .NET Framework – as long as compatible tooling is used for build and restore.

Package Source Mapping example configuration

To opt into this feature, you must have a nuget.config file. Having a single nuget.config at the root of your repository is considered a best practice and can be generated using the dotnet new nugetconfig command. See our nuget.config documentation to learn more.

To get started, declare your desired package sources in your nuget.config file. Following your source declarations, add a <packageSourceMapping> element that specifies the desired mappings for each source.

In our example scenario, I declare two sources: nuget.org and contoso.com. I want to make sure my Contoso.* packages only restore from contoso.com while everything else comes from nuget.org. The following configuration accomplishes this:

<!-- Define a global packages folder for your repository. -->
<!-- This is where installed packages will be stored locally. -->
<config>
  <add key="globalPackagesFolder" value="globalPackagesFolder" />
</config>

<!-- Define my package sources, nuget.org and contoso.com. -->
<!-- `clear` ensures no additional sources are inherited from another config file. -->
<packageSources>
  <clear />
  <!-- `key` can be any identifier for your source. -->
  <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
  <add key="contoso.com" value="https://contoso.com/packages/" />
</packageSources>

<!-- Define mappings by adding package ID patterns beneath the target source. -->
<!-- Contoso.* packages will be restored from contoso.com, everything else from nuget.org. -->
<packageSourceMapping>
  <!-- key value for <packageSource> should match key values from <packageSources> element -->
  <packageSource key="nuget.org">
    <package pattern="*" />
  </packageSource>
  <packageSource key="contoso.com">
    <package pattern="Contoso.*" />
  </packageSource>
</packageSourceMapping>

You may notice that a package ID beginning with the Contoso.* prefix technically matches both the Contoso.* and * patterns. Contoso.* packages will be reliably restored from contoso.com because the most specific matching patterns have precedence. See the Package Source Mapping rules to learn about the precedence rules and more.

Setting default sources

The * pattern in the example configuration makes nuget.org my default source – meaning any package that doesn’t match other specified patterns will be restored from nuget.org without throwing an error. This configuration is advantageous if I primarily use packages from nuget.org, only have a few internal packages, or use standard prefixes for all internal packages like Contoso.*.

If your team doesn’t use standard prefixes for internal package IDs or vets nuget.org packages prior to installation, then making a private source the default will suit your needs better. To make my private source contoso.com the default and only allow Microsoft.* packages from nuget.org, I can use the following configuration:

<!-- Microsoft.* packages will only come from nuget.org. -->
<!-- All other packages come from contoso.com by default. -->
<packageSourceMapping>
  <packageSource key="nuget.org">
    <package pattern="Microsoft.*" />
  </packageSource>
  <packageSource key="contoso.com">
    <package pattern="*" />
  </packageSource>
</packageSoureMapping>

Package Source Mapping rules

  1. Two types of package ID patterns are supported:

    a. NuGet.* – Package prefixes. Must end with a *, which may match 0 or more characters. * is the broadest valid prefix that matches all package IDs, but will have the lowest precedence by default. NuGet* is also valid and will match package IDs NuGet, NuGetFoo, and NuGet.Bar.

    b. NuGet.Common – Exact package IDs.

  2. Any requested package ID must map to one or more sources by matching a defined package ID pattern. In other words, once you have defined a packageSourceMapping element you must explicitly define which sources every package – including transitive packages – will be restored from.

    a. Both top-level (directly installed) and transitive packages must match defined patterns. There is no requirement that a top level package and its dependencies come from the same source.

    b. The same ID pattern can be defined on multiple sources, allowing matching package IDs to be restored from any of the feeds that define the pattern. However, this isn’t recommended due to the impact on restore predictability (a given package could come from multiple sources).

  3. When multiple unique patterns match a package ID, the most specific (longest) match will be preferred.

    a. Exact package ID patterns always have the highest precedence while the generic * always has the lowest precedence. For an example package ID NuGet.Common, the following package ID patterns are ordered from highest to lowest precedence: NuGet.Common, NuGet.*, *.

  4. Package Source Mapping settings are applied following nuget.config precedence rules when multiple nuget.config files at various levels (machine-level, user-level, repo-level) are present.

Important: When the requested package already exists in the global packages folder, no source look-up will happen and the mappings will be ignored. Declare a global packages folder for your repo to gain the full security benefits of this feature. Work to improve the experience with the default global packages folder in planned for a next iteration.

We’ve curated several example scenarios to help you get a better understand of how these rules work and how to best leverage Package Source Mapping for your needs!

Get started

To get started with Package Source Mapping, download your tooling of choice:

To fully onboard your repository take the following steps:

  1. Declare a new global packages folder for your repo.
  2. Run dotnet list package --include-transitive to view all top-level and transitive packages in your solution.
    • For .NET framework projects using packages.config, the packages.config file will have a flat list of all direct and transitive packages.
  3. Define mappings such that every package ID in your solution – including transitive packages – matches a package ID pattern for the target source.
  4. Run restore to validate that you have configured your mappings correctly. If your mappings don’t fully cover every package ID in your solution, the error messages will help you identify the issue.
  5. When restore succeeds, you are done! Optionally consider:

For an idea of how your source mappings may look like, refer to our samples repo.

We want to hear your feedback!

You can provide feedback for this feature at NuGet/Home!

For more general NuGet feedback and suggestions:

3 comments

Leave a comment

  • Rand Random

    Is it possible to specify this inside csproj, I would like to define it differently based on the build configuration Release/Debug?

    • Kartheek PenagamuriMicrosoft employee

      Package source mapping settings can only be mentioned in the nuget.config file. The settings are independent of the build configuration.