Menu
Free Pack
Download BuildMaster Free Trial

Manage NuGet Dependencies With Lockfiles and Package Consumers

by Eric Seng, on Dec 9, 2021 3:21:40 PM

“It worked when I built it” or “It works on my machine”. These are typically followed by frustrated moans and an investigation to identify the source of the problem. Unfortunately, NuGet packages and their dependencies are one common reason these situations occur.

Package dependencies are a lot more complex than they seem on the surface. If you’re not careful, NuGet can resolve them in a way that leads to unwanted packages being introduced into your code, perhaps even into production!

In this article I will:

The Unintended Side Effects of Dependency Resolution

NuGet Packages almost always depend on other packages. This results in a dependency tree that must be resolved for your project to build.

[Butterfinger] diagram_CMYK_202111_Manage package Dependencies

As you can see from the diagram above, this can quickly become difficult, especially considering that even the wrong version of the correct package will prevent dependency resolution. This is typically solved by using version ranges.

Version ranges are used to specify a range of versions your package will accept. This is extremely helpful when resolving dependency trees, but it has some unintended consequences.

Picture3

Lets say you are using the package Oracle.ManagedDataAcess.Core 3.21.4. This package is dependent on the System.DirectoryServices.Protocols packageTo resolve this dependency, a version range is used to tell Oracle.ManagedDataAcess.Core to accept any version of System.DirectoryServices.Protocols that is greater than or equal to version 5.0.0

Pretty convenient right? Yes, it is. But it comes at a cost!

Picture4

The image above contains a list of version updates for package System.DirectoryServices.Protocols. Version 5.0.1 was released 7 days ago (as of the time of writing) and version 6.0 is due to replace it soon. Since the version range for Oracle.ManagedDataAcess.Core was set to accept any version of 5.0.0 or higher, this means that your application would automatically include a new package - perhaps, an unwanted one.

The unexpected acceptance of unapproved third-party packages causes two common issues:

  1. Unpredictable builds that lead to "it worked when I built it yesterday" situations.
  2. Unsafe packages sneaking into your code.

Version ranges make solving dependencies much easier, but the issues that come with them need to be mitigated. Fortunately, this can be accomplished using lock files.

Best Practice: Use Lock Files for Repeatable Builds

Lock files "lock” all versions for the entire dependency tree at the time that the lock file is created and show you every package dependency being used by an application.

Why is this so helpful?

The NuGet client resolves the most current version of a package at build-time, during the dependencies install of a package. This could have been different than the versions that was originally intended at development time. If you don’t “lock” the package versions when you develop an application, your build may change the next time you or someone on your team builds the application. 

To enable the use of lock file with NuGet, set the MSBuild property RestorePackagesWithLockFile in your project file:

<PropertyGroup>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>

If this property is set, NuGet restore will generate a lock file – packages.lock.json file at the project root directory. Besides locking in the package versions, the lock file will also show all the packages your application depends on.

picture6

Lock files solve the massive headache of unpredictable package versions sneaking into production... but they don’t eliminate the problems with dependencies completely.

Watch out for New Dependencies 

Keep in mind that dependencies can - and often will - have dependencies. And these dependencies can change, which means you may find yourself with different packages altogether when you update. And these packages can have license, vulnerability, and quality risks.

The best way mitigate this risk is by setting up a package approval workflow that only allows approved packages to be used by developers and build servers. 

Watch out for New Vulnerabilities

Even after you've started utilizing lock files and have a workflow that makes sure you're only using approved packages - it's entirely possible that a new vulnerability will be discovered in a dependency. 

This vulnerability could then impact any applications that use it. So what can you do?

You could manually inspect the lock files of all your applications to see which ones are using the unwanted dependency. That's a lot of repositories to check-out, and a lot of searching.

But you can also use ProGet's Package Consumer Feature to do this automatically.

Best Practice: Use Package Consumers to Track Dependencies

ProGet's Package Consumers feature shows all the applications that are "consuming" or using a specific package. So lets say the package dependency in question is System.DirectoryServices.Protocols 5.0.0.

After building your application, ProGet's Package Consumers feature uses pgscan to scan the build output, search for the specific package versions consumed by the application and publish that data to ProGet along with your application's name and version.

picture9

Using Package Consumer we can see that the package System.DirectoryServices.Protocols 5.0.0. is being used in applications ThatOtherAPP, MyApp, & OtherApp. Now you know exactly which applications will be affected and can quickly make the relevant changes!

By the way... this isn’t just an example for theoretical purposes.

System.DirectoryServices.Protocols has been downloaded 17.6M times and almost all of them were before the recently discovered vulnerability:

picture10

Anyone that has an application using a package that depends on System.DirectoryServices.Protocols now suddenly has a vulnerable package in their code. Anyone that finds themselves in this situation will definitely want to know which applications are using this package as quickly as possible. Which is exactly why we created package consumers 😀.

NuGet in the Enterprise

Utilizing lockfiles, a package approval workflow, and ProGet’s package consumer will help ensure predictable builds and keep unwanted packages out of them, but there is still a lot more to learn if you want to effectively use NuGet in the enterprise.

Check out our free guide to learn more!

Topics:ProGetNuGet

Related Posts

About Inedo

Inedo is a software product company bringing you the "tech behind the tech."

Makers of Windows-first, enterprise DevOps tools BuildMaster CI/CD, ProGet private package management, and Otter IaC. Maximize developer time, minimize release risk, and empower stakeholders to bring their vision to life faster, all with the people and technology you have right now.

Follow us on social media

Follow Inedo on YouTube Follow Inedo on Facebook Follow Inedo Twitter New call-to-action

Free e-books

Free CICD Book Free dotnet book free IaC book Jenkins CICD Guide