Cyber Security  

NuGet Package Source Mapping: Preventing Dependency Confusion in .NET

Introduction

Modern .NET applications rarely depend only on code written inside the organization. A typical project can reference dozens or even hundreds of NuGet packages, and those packages can introduce their own dependency trees.

That makes package management a security concern.

One particularly dangerous attack is dependency confusion. An attacker publishes a malicious package using the same package ID as an internal package, but places it in a public package source. If the build system resolves the malicious package instead of the intended internal package, untrusted code can enter the application during restore.

For .NET teams using multiple package sources, NuGet Package Source Mapping provides an important defense.

Instead of allowing NuGet to consider every configured source for every package, source mapping explicitly defines which package patterns are allowed to come from which source.

The resulting model is simple:

Package
   |
   v
Does the package match an approved pattern?
   |
   +---- No ----> Restore fails
   |
   +---- Yes
          |
          v
    Approved source

This article explains how dependency confusion happens, how Package Source Mapping works, how to configure it in a .NET repository, and how to test the configuration in CI/CD.

What Is Dependency Confusion?

Consider an organization that has an internal package:

Company.Logging

The package is hosted on an internal NuGet server:

Internal Feed
    |
    +--> Company.Logging

The project contains:

<ItemGroup>
  <PackageReference Include="Company.Logging" Version="4.2.0" />
</ItemGroup>

Now imagine an attacker publishes another package named:

Company.Logging

to a public package source.

If the build environment considers both sources and does not have a strict source policy, the restore process can potentially select the unintended package.

The dangerous part is that the developer may not notice anything unusual.

The package name looks correct.

The project still builds.

The malicious code may execute during package installation, build, application startup, or runtime depending on what the package contains and how it is used.

Why Multiple Package Sources Increase Risk

A common NuGet.Config might contain:

<packageSources>
  <add key="nuget.org"
       value="https://api.nuget.org/v3/index.json" />

  <add key="company"
       value="https://packages.example.com/nuget/v3/index.json" />
</packageSources>

This is convenient because developers can consume:

Public packages -> nuget.org
Internal packages -> company feed

But the restore process needs to know which package belongs to which source.

Without an explicit mapping policy, package-source selection can become broader than intended.

A secure architecture should make the relationship explicit:

Newtonsoft.Json
     |
     +--> nuget.org

Company.Logging
     |
     +--> company feed

What Is Package Source Mapping?

Package Source Mapping allows a NuGet.Config file to associate package ID patterns with package sources.

For example:

<packageSourceMapping>
  <packageSource key="company">
    <package pattern="Company.*" />
  </packageSource>

  <packageSource key="nuget.org">
    <package pattern="*" />
  </packageSource>
</packageSourceMapping>

This configuration says:

Company.* packages
    -> company source

Everything else
    -> nuget.org

The mapping is based on package ID patterns.

This gives the restore process an explicit source-selection policy instead of leaving every package eligible for every configured source.

Why the Wildcard Requires Care

The following configuration is common:

<package pattern="*" />

It means that the source can provide all packages that are not otherwise restricted by more specific mappings.

That can be useful for a public source, but teams need to understand how it interacts with internal package patterns.

For example:

<packageSource key="company">
  <package pattern="Company.*" />
</packageSource>

<packageSource key="nuget.org">
  <package pattern="*" />
</packageSource>

is materially different from giving the public source only a limited set of package patterns.

The first configuration says that Company.* packages are associated with the internal source while the public source remains eligible for the wildcard mapping.

When designing source mapping, test the exact restore behavior for internal, public, transitive, and newly introduced packages.

A Practical NuGet.Config

A repository-level configuration might look like this:

<?xml version="1.0" encoding="utf-8"?>

<configuration>

  <packageSources>
    <clear />

    <add key="company"
         value="https://packages.example.com/nuget/v3/index.json" />

    <add key="nuget.org"
         value="https://api.nuget.org/v3/index.json" />
  </packageSources>

  <packageSourceMapping>

    <packageSource key="company">
      <package pattern="Company.*" />
      <package pattern="Contoso.*" />
    </packageSource>

    <packageSource key="nuget.org">
      <package pattern="*" />
    </packageSource>

  </packageSourceMapping>

</configuration>

The <clear /> is important in controlled environments because it prevents inherited package sources from silently changing the effective configuration.

Without it, a developer machine or parent configuration file could introduce an additional package source.

Mapping Exact Packages

You do not always need a namespace-style pattern.

For example:

<packageSource key="company">
  <package pattern="Company.Logging" />
  <package pattern="Company.Security" />
</packageSource>

This provides a much narrower policy.

It is useful when an organization owns only a small number of internal packages.

The trade-off is maintenance.

Every new internal package requires a configuration change.

Mapping Package Families

For organizations with many internal packages, package prefixes are usually easier to manage.

<packageSource key="company">
  <package pattern="Company.*" />
</packageSource>

This allows:

Company.Logging
Company.Security
Company.Data
Company.Messaging

while keeping the policy manageable.

The package naming convention therefore becomes part of the security model.

A consistent internal prefix makes source mapping substantially easier to operate.

Transitive Dependencies Matter

A common mistake is to map only the packages directly referenced by the application.

Suppose the project contains:

<PackageReference Include="Company.Web" Version="5.0.0" />

and Company.Web depends on:

Company.Logging
Company.Security

Those packages are transitive dependencies.

The source policy needs to account for them as well.

A secure package architecture therefore considers:

Application
    |
    +--> Direct dependency
             |
             +--> Transitive dependency
                       |
                       +--> Package source

Do not assume that protecting direct dependencies automatically protects the complete dependency graph.

Source Mapping and Dependency Confusion

The security model becomes:

Package ID
   |
   v
Source Mapping
   |
   +---- Approved source
   |
   +---- No valid mapping
              |
              v
            FAIL

Suppose an attacker publishes:

Company.Security

to a public feed.

If the organization's policy requires:

<packageSource key="company">
  <package pattern="Company.*" />
</packageSource>

the package ID is associated with the intended internal source rather than simply being treated as an unrestricted public dependency.

The important security principle is:

Package identity and package source should be treated as related security properties.

Central Package Management

Many organizations use Central Package Management so package versions are defined centrally.

For example:

<ItemGroup>
  <PackageVersion Include="Company.Logging"
                  Version="4.2.0" />

  <PackageVersion Include="Serilog"
                  Version="4.0.0" />
</ItemGroup>

Central package management controls versions, but it does not replace source mapping.

You still need to answer:

Which version?
        +
Which source?

A secure dependency-management system should address both.

Combining Source Mapping With Package Lock Files

Package source mapping is also complementary to package locking.

A lock file can help make restore behavior reproducible by recording the resolved dependency graph.

The controls solve different problems:

Source Mapping
    |
    +--> Where can this package come from?


Lock File
    |
    +--> Which dependency graph was resolved?

Using both can provide stronger supply-chain controls.

The exact configuration depends on whether your repository requires floating versions, centrally managed dependencies, or fully reproducible restores.

Testing Source Mapping Locally

Do not assume that a valid-looking NuGet.Config is enough.

Test the configuration by restoring the actual project.

For example:

dotnet restore --configfile ./NuGet.Config

Then inspect the restore output for unexpected source behavior.

For CI, make the repository configuration explicit:

dotnet restore \
  --configfile ./NuGet.Config \
  --locked-mode

if your repository uses package locking.

The important point is that CI should use the same source policy that developers expect locally.

Testing an Internal Package

Create a test package with an internal prefix:

Company.Test.Package

Verify that restore resolves it from the internal feed.

Then temporarily make the package unavailable from the internal source and confirm that restore does not silently obtain it from an unintended public source.

The expected behavior is:

Internal package unavailable
        |
        v
Restore fails

rather than:

Internal package unavailable
        |
        v
Public source selected
        |
        v
Build continues

That negative test is particularly important.

Testing Unknown Internal Package Names

Suppose the organization uses:

Company.*

and a developer introduces:

Company.NewFeature

The test should verify that the package is correctly routed to the intended internal source.

Likewise, a typo such as:

Compny.Logging

should not accidentally become a public package.

This is one reason naming conventions should be predictable and reviewed.

Testing CI/CD

The CI pipeline should explicitly use the repository's package configuration.

For example:

steps:
  - script: |
      dotnet restore \
        --configfile ./NuGet.Config
    displayName: Restore dependencies

  - script: |
      dotnet build --no-restore
    displayName: Build

The pipeline should not silently rely on a developer's machine-level NuGet configuration.

A useful security test is to run CI with only the expected package sources available.

This makes unexpected dependencies easier to detect.

Checking for Configuration Drift

Package-source security can weaken over time.

For example:

Month 1
Internal + Public

Month 6
Internal + Public + Temporary Feed

Month 12
Internal + Public + Temporary Feed + Developer Feed

If source configuration is not reviewed, the package trust boundary gradually becomes larger.

Store NuGet.Config in source control and review changes like application code.

Changes to:

<packageSources>

and:

<packageSourceMapping>

should receive security review where appropriate.

Common Mistakes

Assuming Package Names Are Globally Trusted

A package ID does not automatically tell you which source should be trusted.

Mapping Only Direct Dependencies

Transitive packages also need to be considered.

Allowing Uncontrolled Package Sources

Developer-specific feeds can change restore behavior.

Relying Only on Package Versions

A pinned version does not by itself answer whether the package came from the intended source.

Ignoring Configuration Inheritance

Parent or user-level configuration can introduce unexpected sources.

Testing Only Successful Restores

A secure policy must also fail correctly when a package cannot be obtained from its approved source.

Using Broad Wildcards Without Understanding Them

Wildcard mappings are powerful and convenient, but their effect should be explicitly tested.

Best Practices

  1. Store NuGet source configuration in the repository.

  2. Use <clear /> when you need a controlled source set.

  3. Map internal package prefixes to trusted internal feeds.

  4. Keep public package sources explicitly defined.

  5. Consider transitive dependencies when designing the mapping.

  6. Use consistent naming conventions for internal packages.

  7. Review changes to package-source configuration.

  8. Run restore using the repository configuration in CI/CD.

  9. Test both allowed and denied restore scenarios.

  10. Combine source mapping with dependency locking where reproducibility is required.

  11. Avoid unnecessary package sources.

  12. Treat package restore as part of the software supply-chain security boundary.

Frequently Asked Questions

Does Package Source Mapping prevent malicious packages completely?

No security mechanism provides absolute protection. Source mapping reduces the risk of a package being restored from an unintended source, but organizations should combine it with package review, vulnerability scanning, dependency controls, and secure CI/CD practices.

Should every internal package have a unique prefix?

A consistent organization-owned prefix is highly useful because it allows packages to be mapped to an internal source using predictable patterns.

Does source mapping affect transitive dependencies?

Yes. The package source policy applies to packages being restored, so the complete dependency graph needs to be considered.

Is package locking a replacement for source mapping?

No. Package locking and source mapping address different aspects of dependency security and reproducibility.

Should NuGet.Config be committed to source control?

For repositories with controlled package-source requirements, keeping the relevant configuration in source control makes the restore policy visible, reviewable, and reproducible.

What should happen when an internal package is unavailable?

A secure configuration should fail the restore rather than silently obtaining a package from an unintended source.

Conclusion

NuGet dependency management is more than selecting package versions. In organizations that use both public and private feeds, the package source itself is part of the application's security boundary.

Dependency confusion attacks exploit ambiguity between package identity and package origin. Package Source Mapping addresses that ambiguity by allowing teams to explicitly associate package patterns with trusted sources.

The strongest implementation combines source mapping with controlled NuGet configuration, consistent internal package naming, dependency locking where appropriate, CI/CD enforcement, and negative security tests.

The most important principle is straightforward:

A package should come from a source because your policy says it can, not simply because that source happens to contain a package with the right name.

By making package-source selection explicit, .NET teams can significantly reduce one important class of dependency-supply-chain risk while making restores more predictable and easier to audit.