From 9eb75832427219b46cf01a1c1443843c923a53aa Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 27 Feb 2026 14:17:29 +0000 Subject: [PATCH 01/20] Refactor `GetReachableNuGetFeeds` out of `GetReachableFallbackNugetFeeds` --- .../NugetPackageRestorer.cs | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 1d01412ee051..33e8db19eab4 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -192,6 +192,34 @@ public HashSet Restore() return assemblyLookupLocations; } + /// + /// Tests which of the feeds given by are reachable. + /// + /// The feeds to check. + /// Whether the feeds are fallback feeds or not. + /// The list of feeds that could be reached. + private List GetReachableNuGetFeeds(HashSet feedsToCheck, bool isFallback) + { + var fallbackStr = isFallback ? "fallback " : ""; + logger.LogInfo($"Checking {fallbackStr}NuGet feed reachability on feeds: {string.Join(", ", feedsToCheck.OrderBy(f => f))}"); + + var (initialTimeout, tryCount) = GetFeedRequestSettings(isFallback); + var reachableFeeds = feedsToCheck + .Where(feed => IsFeedReachable(feed, initialTimeout, tryCount, allowExceptions: false)) + .ToList(); + + if (reachableFeeds.Count == 0) + { + logger.LogWarning($"No {fallbackStr}NuGet feeds are reachable."); + } + else + { + logger.LogInfo($"Reachable {fallbackStr}NuGet feeds: {string.Join(", ", reachableFeeds.OrderBy(f => f))}"); + } + + return reachableFeeds; + } + private List GetReachableFallbackNugetFeeds(HashSet? feedsFromNugetConfigs) { var fallbackFeeds = EnvironmentVariables.GetURLs(EnvironmentVariableNames.FallbackNugetFeeds).ToHashSet(); @@ -212,21 +240,7 @@ private List GetReachableFallbackNugetFeeds(HashSet? feedsFromNu } } - logger.LogInfo($"Checking fallback NuGet feed reachability on feeds: {string.Join(", ", fallbackFeeds.OrderBy(f => f))}"); - var (initialTimeout, tryCount) = GetFeedRequestSettings(isFallback: true); - var reachableFallbackFeeds = fallbackFeeds.Where(feed => IsFeedReachable(feed, initialTimeout, tryCount, allowExceptions: false)).ToList(); - if (reachableFallbackFeeds.Count == 0) - { - logger.LogWarning("No fallback NuGet feeds are reachable."); - } - else - { - logger.LogInfo($"Reachable fallback NuGet feeds: {string.Join(", ", reachableFallbackFeeds.OrderBy(f => f))}"); - } - - compilationInfoContainer.CompilationInfos.Add(("Reachable fallback NuGet feed count", reachableFallbackFeeds.Count.ToString())); - - return reachableFallbackFeeds; + return GetReachableNuGetFeeds(fallbackFeeds, isFallback: true); } /// From b01a14f504cad718716337bb2451056730c64348 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 27 Feb 2026 14:23:03 +0000 Subject: [PATCH 02/20] Use `GetReachableNuGetFeeds` in `CheckSpecifiedFeeds` --- .../NugetPackageRestorer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 33e8db19eab4..5d7ad8f6124a 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -767,8 +767,7 @@ private bool CheckFeeds(out HashSet explicitFeeds, out HashSet a /// True if all feeds are reachable or false otherwise. private bool CheckSpecifiedFeeds(HashSet feeds) { - logger.LogInfo("Checking that NuGet feeds are reachable..."); - + // Exclude any feeds that are configured by the corresponding environment variable. var excludedFeeds = EnvironmentVariables.GetURLs(EnvironmentVariableNames.ExcludedNugetFeedsFromResponsivenessCheck) .ToHashSet(); @@ -777,9 +776,10 @@ private bool CheckSpecifiedFeeds(HashSet feeds) logger.LogInfo($"Excluded NuGet feeds from responsiveness check: {string.Join(", ", excludedFeeds.OrderBy(f => f))}"); } - var (initialTimeout, tryCount) = GetFeedRequestSettings(isFallback: false); + var feedsToCheck = feeds.Where(feed => !excludedFeeds.Contains(feed)).ToHashSet(); + var reachableFeeds = this.GetReachableNuGetFeeds(feedsToCheck, isFallback: false); + var allFeedsReachable = reachableFeeds.Count == feedsToCheck.Count; - var allFeedsReachable = feeds.All(feed => excludedFeeds.Contains(feed) || IsFeedReachable(feed, initialTimeout, tryCount)); if (!allFeedsReachable) { logger.LogWarning("Found unreachable NuGet feed in C# analysis with build-mode 'none'. This may cause missing dependencies in the analysis."); From bf29e94ecdd8e9de473b0a308d3fbcc8f8492001 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 27 Feb 2026 14:24:55 +0000 Subject: [PATCH 03/20] Inline `CheckFeeds` --- .../NugetPackageRestorer.cs | 64 +++++++++---------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 5d7ad8f6124a..7bdec255ab4f 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -119,13 +119,37 @@ public HashSet Restore() try { - if (checkNugetFeedResponsiveness && !CheckFeeds(out explicitFeeds, out allFeeds)) + if (checkNugetFeedResponsiveness) { - // todo: we could also check the reachability of the inherited nuget feeds, but to use those in the fallback we would need to handle authentication too. - var unresponsiveMissingPackageLocation = DownloadMissingPackagesFromSpecificFeeds([], explicitFeeds); - return unresponsiveMissingPackageLocation is null - ? [] - : [unresponsiveMissingPackageLocation]; + // Find feeds that are configured in NuGet.config files and divide them into ones that + // are explicitly configured for the project, and "all feeds" (including inherited ones) + // from other locations on the host outside of the working directory. + (explicitFeeds, allFeeds) = GetAllFeeds(); + var inheritedFeeds = allFeeds.Except(explicitFeeds).ToHashSet(); + + // Check whether the explicit feeds can be reached. + HashSet feedsToCheck = explicitFeeds; + + // If private package registries are configured for C#, then check those + // in addition to the ones that are configured in `nuget.config` files. + this.dependabotProxy?.RegistryURLs.ForEach(url => feedsToCheck.Add(url)); + + var explicitFeedsReachable = this.CheckSpecifiedFeeds(feedsToCheck); + + if (inheritedFeeds.Count > 0) + { + logger.LogInfo($"Inherited NuGet feeds (not checked for reachability): {string.Join(", ", inheritedFeeds.OrderBy(f => f))}"); + compilationInfoContainer.CompilationInfos.Add(("Inherited NuGet feed count", inheritedFeeds.Count.ToString())); + } + + if (!explicitFeedsReachable) + { + // todo: we could also check the reachability of the inherited nuget feeds, but to use those in the fallback we would need to handle authentication too. + var unresponsiveMissingPackageLocation = DownloadMissingPackagesFromSpecificFeeds([], explicitFeeds); + return unresponsiveMissingPackageLocation is null + ? [] + : [unresponsiveMissingPackageLocation]; + } } using (var nuget = new NugetExeWrapper(fileProvider, legacyPackageDirectory, logger)) @@ -732,34 +756,6 @@ private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, return (timeoutMilliSeconds, tryCount); } - /// - /// Checks that we can connect to all NuGet feeds that are explicitly configured in configuration files - /// as well as any private package registry feeds that are configured. - /// - /// Outputs the set of explicit feeds. - /// Outputs the set of all feeds (explicit and inherited). - /// True if all feeds are reachable or false otherwise. - private bool CheckFeeds(out HashSet explicitFeeds, out HashSet allFeeds) - { - (explicitFeeds, allFeeds) = GetAllFeeds(); - HashSet feedsToCheck = explicitFeeds; - - // If private package registries are configured for C#, then check those - // in addition to the ones that are configured in `nuget.config` files. - this.dependabotProxy?.RegistryURLs.ForEach(url => feedsToCheck.Add(url)); - - var allFeedsReachable = this.CheckSpecifiedFeeds(feedsToCheck); - - var inheritedFeeds = allFeeds.Except(explicitFeeds).ToHashSet(); - if (inheritedFeeds.Count > 0) - { - logger.LogInfo($"Inherited NuGet feeds (not checked for reachability): {string.Join(", ", inheritedFeeds.OrderBy(f => f))}"); - compilationInfoContainer.CompilationInfos.Add(("Inherited NuGet feed count", inheritedFeeds.Count.ToString())); - } - - return allFeedsReachable; - } - /// /// Checks that we can connect to the specified NuGet feeds. /// From e6871962ec0836e411e7136901873eec24fe8d42 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 27 Feb 2026 14:25:58 +0000 Subject: [PATCH 04/20] Use `explicitFeeds` directly --- .../NugetPackageRestorer.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 7bdec255ab4f..4a4029507d18 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -127,14 +127,11 @@ public HashSet Restore() (explicitFeeds, allFeeds) = GetAllFeeds(); var inheritedFeeds = allFeeds.Except(explicitFeeds).ToHashSet(); - // Check whether the explicit feeds can be reached. - HashSet feedsToCheck = explicitFeeds; - - // If private package registries are configured for C#, then check those + // If private package registries are configured for C#, then consider those // in addition to the ones that are configured in `nuget.config` files. - this.dependabotProxy?.RegistryURLs.ForEach(url => feedsToCheck.Add(url)); + this.dependabotProxy?.RegistryURLs.ForEach(url => explicitFeeds.Add(url)); - var explicitFeedsReachable = this.CheckSpecifiedFeeds(feedsToCheck); + var explicitFeedsReachable = this.CheckSpecifiedFeeds(explicitFeeds); if (inheritedFeeds.Count > 0) { @@ -191,6 +188,7 @@ public HashSet Restore() logger.LogError($"Failed to restore NuGet packages with nuget.exe: {exc.Message}"); } + // Restore project dependencies with `dotnet restore`. var restoredProjects = RestoreSolutions(out var container); var projects = fileProvider.Projects.Except(restoredProjects); RestoreProjects(projects, allFeeds, out var containers); From 65953c0cfe1cabca343ad39abb23f903ded8fdba Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 27 Feb 2026 14:30:57 +0000 Subject: [PATCH 05/20] Divide up `CheckSpecifiedFeeds` --- .../NugetPackageRestorer.cs | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 4a4029507d18..f300865d2aeb 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -755,13 +755,10 @@ private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, } /// - /// Checks that we can connect to the specified NuGet feeds. + /// Retrieves a list of excluded NuGet feeds from the corresponding environment variable. /// - /// The set of package feeds to check. - /// True if all feeds are reachable or false otherwise. - private bool CheckSpecifiedFeeds(HashSet feeds) + private HashSet GetExcludedFeeds() { - // Exclude any feeds that are configured by the corresponding environment variable. var excludedFeeds = EnvironmentVariables.GetURLs(EnvironmentVariableNames.ExcludedNugetFeedsFromResponsivenessCheck) .ToHashSet(); @@ -770,10 +767,35 @@ private bool CheckSpecifiedFeeds(HashSet feeds) logger.LogInfo($"Excluded NuGet feeds from responsiveness check: {string.Join(", ", excludedFeeds.OrderBy(f => f))}"); } + return excludedFeeds; + } + + /// + /// Checks that we can connect to the specified NuGet feeds. + /// + /// The set of package feeds to check. + /// True if all feeds are reachable or false otherwise. + private bool CheckSpecifiedFeeds(HashSet feeds) + { + // Exclude any feeds that are configured by the corresponding environment variable. + var excludedFeeds = GetExcludedFeeds(); + var feedsToCheck = feeds.Where(feed => !excludedFeeds.Contains(feed)).ToHashSet(); var reachableFeeds = this.GetReachableNuGetFeeds(feedsToCheck, isFallback: false); var allFeedsReachable = reachableFeeds.Count == feedsToCheck.Count; + this.EmitUnreachableFeedsDiagnostics(allFeedsReachable); + + return allFeedsReachable; + } + + /// + /// If is `false`, logs this and emits a diagnostic. + /// Adds a `CompilationInfos` entry either way. + /// + /// Whether all feeds were reachable or not. + private void EmitUnreachableFeedsDiagnostics(bool allFeedsReachable) + { if (!allFeedsReachable) { logger.LogWarning("Found unreachable NuGet feed in C# analysis with build-mode 'none'. This may cause missing dependencies in the analysis."); @@ -787,8 +809,6 @@ private bool CheckSpecifiedFeeds(HashSet feeds) )); } compilationInfoContainer.CompilationInfos.Add(("All NuGet feeds reachable", allFeedsReachable ? "1" : "0")); - - return allFeedsReachable; } private IEnumerable GetFeeds(Func> getNugetFeeds) From 7ff355bd1fc237bf7d24956e6bdc63d15fd1c2db Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 27 Feb 2026 14:32:01 +0000 Subject: [PATCH 06/20] Check reachability of inherited feeds --- .../NugetPackageRestorer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index f300865d2aeb..474b3f6c7f2c 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -132,10 +132,10 @@ public HashSet Restore() this.dependabotProxy?.RegistryURLs.ForEach(url => explicitFeeds.Add(url)); var explicitFeedsReachable = this.CheckSpecifiedFeeds(explicitFeeds); + this.GetReachableNuGetFeeds(inheritedFeeds, isFallback: false); if (inheritedFeeds.Count > 0) { - logger.LogInfo($"Inherited NuGet feeds (not checked for reachability): {string.Join(", ", inheritedFeeds.OrderBy(f => f))}"); compilationInfoContainer.CompilationInfos.Add(("Inherited NuGet feed count", inheritedFeeds.Count.ToString())); } From eaebca72b673469491843e6c9da528a0e8e99605 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 27 Feb 2026 14:38:01 +0000 Subject: [PATCH 07/20] Only use reachable feeds when private registries are configured --- .../NugetPackageRestorer.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 474b3f6c7f2c..0e8134bd19ba 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -116,6 +116,7 @@ public HashSet Restore() HashSet? explicitFeeds = null; HashSet? allFeeds = null; + HashSet? reachableFeeds = []; try { @@ -131,8 +132,11 @@ public HashSet Restore() // in addition to the ones that are configured in `nuget.config` files. this.dependabotProxy?.RegistryURLs.ForEach(url => explicitFeeds.Add(url)); - var explicitFeedsReachable = this.CheckSpecifiedFeeds(explicitFeeds); - this.GetReachableNuGetFeeds(inheritedFeeds, isFallback: false); + var (explicitFeedsReachable, reachableExplicitFeeds) = + this.CheckSpecifiedFeeds(explicitFeeds); + reachableFeeds.UnionWith(reachableExplicitFeeds); + + reachableFeeds.UnionWith(this.GetReachableNuGetFeeds(inheritedFeeds, isFallback: false)); if (inheritedFeeds.Count > 0) { @@ -191,7 +195,7 @@ public HashSet Restore() // Restore project dependencies with `dotnet restore`. var restoredProjects = RestoreSolutions(out var container); var projects = fileProvider.Projects.Except(restoredProjects); - RestoreProjects(projects, allFeeds, out var containers); + RestoreProjects(projects, reachableFeeds, out var containers); var dependencies = containers.Flatten(container); @@ -774,8 +778,11 @@ private HashSet GetExcludedFeeds() /// Checks that we can connect to the specified NuGet feeds. /// /// The set of package feeds to check. - /// True if all feeds are reachable or false otherwise. - private bool CheckSpecifiedFeeds(HashSet feeds) + /// + /// True if all feeds are reachable or false otherwise. + /// Also returns the list of reachable feeds. + /// + private (bool, List) CheckSpecifiedFeeds(HashSet feeds) { // Exclude any feeds that are configured by the corresponding environment variable. var excludedFeeds = GetExcludedFeeds(); @@ -786,7 +793,7 @@ private bool CheckSpecifiedFeeds(HashSet feeds) this.EmitUnreachableFeedsDiagnostics(allFeedsReachable); - return allFeedsReachable; + return (allFeedsReachable, reachableFeeds); } /// From 53c286762ba66f03574277fd4a23c7e4f550f2bf Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 9 Apr 2026 13:06:32 +0200 Subject: [PATCH 08/20] C#: Turn checkNugetFeedResponsiveness into a field and remove some explicit this qualifiers. --- .../NugetPackageRestorer.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 0e8134bd19ba..9ccf550a8aab 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -20,6 +20,7 @@ internal sealed partial class NugetPackageRestorer : IDisposable { internal const string PublicNugetOrgFeed = "https://api.nuget.org/v3/index.json"; + private readonly bool checkNugetFeedResponsiveness = EnvironmentVariables.GetBooleanOptOut(EnvironmentVariableNames.CheckNugetFeedResponsiveness); private readonly FileProvider fileProvider; private readonly FileContent fileContent; private readonly IDotNet dotnet; @@ -110,7 +111,6 @@ public DirectoryInfo[] GetOrderedPackageVersionSubDirectories(string packagePath public HashSet Restore() { var assemblyLookupLocations = new HashSet(); - var checkNugetFeedResponsiveness = EnvironmentVariables.GetBooleanOptOut(EnvironmentVariableNames.CheckNugetFeedResponsiveness); logger.LogInfo($"Checking NuGet feed responsiveness: {checkNugetFeedResponsiveness}"); compilationInfoContainer.CompilationInfos.Add(("NuGet feed responsiveness checked", checkNugetFeedResponsiveness ? "1" : "0")); @@ -130,13 +130,12 @@ public HashSet Restore() // If private package registries are configured for C#, then consider those // in addition to the ones that are configured in `nuget.config` files. - this.dependabotProxy?.RegistryURLs.ForEach(url => explicitFeeds.Add(url)); + dependabotProxy?.RegistryURLs.ForEach(url => explicitFeeds.Add(url)); - var (explicitFeedsReachable, reachableExplicitFeeds) = - this.CheckSpecifiedFeeds(explicitFeeds); + var (explicitFeedsReachable, reachableExplicitFeeds) = CheckSpecifiedFeeds(explicitFeeds); reachableFeeds.UnionWith(reachableExplicitFeeds); - reachableFeeds.UnionWith(this.GetReachableNuGetFeeds(inheritedFeeds, isFallback: false)); + reachableFeeds.UnionWith(GetReachableNuGetFeeds(inheritedFeeds, isFallback: false)); if (inheritedFeeds.Count > 0) { @@ -193,6 +192,7 @@ public HashSet Restore() } // Restore project dependencies with `dotnet restore`. + // TODO: We also need to respect reachable feeds for resolution restore. var restoredProjects = RestoreSolutions(out var container); var projects = fileProvider.Projects.Except(restoredProjects); RestoreProjects(projects, reachableFeeds, out var containers); @@ -320,14 +320,14 @@ private void RestoreProjects(IEnumerable projects, HashSet? conf // `nuget.config` files instead of the command-line arguments. string? extraArgs = null; - if (this.dependabotProxy is not null) + if (dependabotProxy is not null) { // If the Dependabot proxy is configured, then our main goal is to make `dotnet` aware // of the private registry feeds. However, since providing them as command-line arguments // to `dotnet` ignores other feeds that may be configured, we also need to add the feeds // we have discovered from analysing `nuget.config` files. var sources = configuredSources ?? new(); - this.dependabotProxy.RegistryURLs.ForEach(url => sources.Add(url)); + dependabotProxy.RegistryURLs.ForEach(url => sources.Add(url)); // Add package sources. If any are present, they override all sources specified in // the configuration file(s). @@ -680,11 +680,11 @@ private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, // Configure the HttpClient to be aware of the Dependabot Proxy, if used. HttpClientHandler httpClientHandler = new(); - if (this.dependabotProxy != null) + if (dependabotProxy != null) { - httpClientHandler.Proxy = new WebProxy(this.dependabotProxy.Address); + httpClientHandler.Proxy = new WebProxy(dependabotProxy.Address); - if (this.dependabotProxy.Certificate != null) + if (dependabotProxy.Certificate != null) { httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, _) => { @@ -699,7 +699,7 @@ private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, return false; } chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; - chain.ChainPolicy.CustomTrustStore.Add(this.dependabotProxy.Certificate); + chain.ChainPolicy.CustomTrustStore.Add(dependabotProxy.Certificate); return chain.Build(cert); }; } @@ -788,10 +788,10 @@ private HashSet GetExcludedFeeds() var excludedFeeds = GetExcludedFeeds(); var feedsToCheck = feeds.Where(feed => !excludedFeeds.Contains(feed)).ToHashSet(); - var reachableFeeds = this.GetReachableNuGetFeeds(feedsToCheck, isFallback: false); + var reachableFeeds = GetReachableNuGetFeeds(feedsToCheck, isFallback: false); var allFeedsReachable = reachableFeeds.Count == feedsToCheck.Count; - this.EmitUnreachableFeedsDiagnostics(allFeedsReachable); + EmitUnreachableFeedsDiagnostics(allFeedsReachable); return (allFeedsReachable, reachableFeeds); } @@ -863,11 +863,11 @@ private IEnumerable GetFeeds(Func> getNugetFeeds) if (invalidNugetConfigs.Count() > 0) { - this.logger.LogWarning(string.Format( + logger.LogWarning(string.Format( "Found incorrectly named NuGet configuration files: {0}", string.Join(", ", invalidNugetConfigs) )); - this.diagnosticsWriter.AddEntry(new DiagnosticMessage( + diagnosticsWriter.AddEntry(new DiagnosticMessage( Language.CSharp, "buildless/case-sensitive-nuget-config", "Found NuGet configuration files which are not correctly named", @@ -933,7 +933,7 @@ private IEnumerable GetFeeds(Func> getNugetFeeds) else { // If we haven't found any `nuget.config` files, then obtain a list of feeds from the root source directory. - allFeeds = GetFeeds(() => dotnet.GetNugetFeedsFromFolder(this.fileProvider.SourceDir.FullName)).ToHashSet(); + allFeeds = GetFeeds(() => dotnet.GetNugetFeedsFromFolder(fileProvider.SourceDir.FullName)).ToHashSet(); } logger.LogInfo($"Found {allFeeds.Count} NuGet feeds (with inherited ones) in nuget.config files: {string.Join(", ", allFeeds.OrderBy(f => f))}"); From 4f2e3f35f5dd05272b8213015a6ce3ce1cd4889a Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 9 Apr 2026 14:32:30 +0200 Subject: [PATCH 09/20] C#: Add private registries to the set of explicit feeds. Always use specific sources for restoring if private registries are used of if nuget feed reachability check is performed. --- .../NugetPackageRestorer.cs | 131 +++++++++--------- 1 file changed, 67 insertions(+), 64 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 9ccf550a8aab..127c9b04f694 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -30,6 +30,8 @@ internal sealed partial class NugetPackageRestorer : IDisposable private readonly DependencyDirectory missingPackageDirectory; private readonly ILogger logger; private readonly ICompilationInfoContainer compilationInfoContainer; + private HashSet PrivateRegistryFeeds => dependabotProxy?.RegistryURLs ?? []; + private bool HasPrivateRegistryFeeds => PrivateRegistryFeeds.Any(); public DependencyDirectory PackageDirectory { get; } @@ -114,34 +116,32 @@ public HashSet Restore() logger.LogInfo($"Checking NuGet feed responsiveness: {checkNugetFeedResponsiveness}"); compilationInfoContainer.CompilationInfos.Add(("NuGet feed responsiveness checked", checkNugetFeedResponsiveness ? "1" : "0")); - HashSet? explicitFeeds = null; - HashSet? allFeeds = null; - HashSet? reachableFeeds = []; + HashSet explicitFeeds = []; + string? explicitRestoreSources = null; try { + HashSet reachableFeeds = []; + HashSet allFeeds = []; + + // Find feeds that are configured in NuGet.config files and divide them into ones that + // are explicitly configured for the project or by a private registry, and "all feeds" + // (including inherited ones) from other locations on the host outside of the working directory. + (explicitFeeds, allFeeds) = GetAllFeeds(); + if (checkNugetFeedResponsiveness) { - // Find feeds that are configured in NuGet.config files and divide them into ones that - // are explicitly configured for the project, and "all feeds" (including inherited ones) - // from other locations on the host outside of the working directory. - (explicitFeeds, allFeeds) = GetAllFeeds(); var inheritedFeeds = allFeeds.Except(explicitFeeds).ToHashSet(); - // If private package registries are configured for C#, then consider those - // in addition to the ones that are configured in `nuget.config` files. - dependabotProxy?.RegistryURLs.ForEach(url => explicitFeeds.Add(url)); - - var (explicitFeedsReachable, reachableExplicitFeeds) = CheckSpecifiedFeeds(explicitFeeds); - reachableFeeds.UnionWith(reachableExplicitFeeds); - - reachableFeeds.UnionWith(GetReachableNuGetFeeds(inheritedFeeds, isFallback: false)); - if (inheritedFeeds.Count > 0) { compilationInfoContainer.CompilationInfos.Add(("Inherited NuGet feed count", inheritedFeeds.Count.ToString())); } + var explicitFeedsReachable = CheckSpecifiedFeeds(explicitFeeds, out var reachableExplicitFeeds); + reachableFeeds.UnionWith(reachableExplicitFeeds); + reachableFeeds.UnionWith(GetReachableNuGetFeeds(inheritedFeeds, isFallback: false)); + if (!explicitFeedsReachable) { // todo: we could also check the reachability of the inherited nuget feeds, but to use those in the fallback we would need to handle authentication too. @@ -150,6 +150,16 @@ public HashSet Restore() ? [] : [unresponsiveMissingPackageLocation]; } + + // If feed responsiveness is being checked, we only want to use the feeds that are reachable (note this set includes private + // registry feeds if they are reachable). + explicitRestoreSources = MakeRestoreSourcesArgument(reachableFeeds); + } + else if (HasPrivateRegistryFeeds) + { + // If private registries are configured they need to be included as sources for the restore, which requires that + // they are provided as source arguments for the restore. The private registries are included in the `allFeeds` set. + explicitRestoreSources = MakeRestoreSourcesArgument(allFeeds); } using (var nuget = new NugetExeWrapper(fileProvider, legacyPackageDirectory, logger)) @@ -192,10 +202,9 @@ public HashSet Restore() } // Restore project dependencies with `dotnet restore`. - // TODO: We also need to respect reachable feeds for resolution restore. - var restoredProjects = RestoreSolutions(out var container); + var restoredProjects = RestoreSolutions(explicitRestoreSources, out var container); var projects = fileProvider.Projects.Except(restoredProjects); - RestoreProjects(projects, reachableFeeds, out var containers); + RestoreProjects(projects, explicitRestoreSources, out var containers); var dependencies = containers.Flatten(container); @@ -277,7 +286,7 @@ private List GetReachableFallbackNugetFeeds(HashSet? feedsFromNu /// Populates dependencies with the relevant dependencies from the assets files generated by the restore. /// Returns a list of projects that are up to date with respect to restore. /// - private IEnumerable RestoreSolutions(out DependencyContainer dependencies) + private IEnumerable RestoreSolutions(string? explicitRestoreSources, out DependencyContainer dependencies) { var successCount = 0; var nugetSourceFailures = 0; @@ -288,7 +297,7 @@ private IEnumerable RestoreSolutions(out DependencyContainer dependencie var projects = fileProvider.Solutions.SelectMany(solution => { logger.LogInfo($"Restoring solution {solution}..."); - var res = dotnet.Restore(new(solution, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true, TargetWindows: isWindows)); + var res = dotnet.Restore(new(solution, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true, ExtraArgs: explicitRestoreSources, TargetWindows: isWindows)); if (res.Success) { successCount++; @@ -307,39 +316,28 @@ private IEnumerable RestoreSolutions(out DependencyContainer dependencie return projects; } + private string? MakeRestoreSourcesArgument(IEnumerable feeds) + { + // Add package sources. If any are present, they override all sources specified in + // the configuration file(s). + var feedArgs = new StringBuilder(); + foreach (var feed in feeds) + { + feedArgs.Append($" -s {feed}"); + } + + return feedArgs.ToString(); + } + /// /// Executes `dotnet restore` on all projects in projects. /// This is done in parallel for performance reasons. /// Populates dependencies with the relative paths to the assets files generated by the restore. /// /// A list of paths to project files. - private void RestoreProjects(IEnumerable projects, HashSet? configuredSources, out ConcurrentBag dependencies) - { - // Conservatively, we only set this to a non-null value if a Dependabot proxy is enabled. - // This ensures that we continue to get the old behaviour where feeds are taken from - // `nuget.config` files instead of the command-line arguments. - string? extraArgs = null; - - if (dependabotProxy is not null) - { - // If the Dependabot proxy is configured, then our main goal is to make `dotnet` aware - // of the private registry feeds. However, since providing them as command-line arguments - // to `dotnet` ignores other feeds that may be configured, we also need to add the feeds - // we have discovered from analysing `nuget.config` files. - var sources = configuredSources ?? new(); - dependabotProxy.RegistryURLs.ForEach(url => sources.Add(url)); - - // Add package sources. If any are present, they override all sources specified in - // the configuration file(s). - var feedArgs = new StringBuilder(); - foreach (string source in sources) - { - feedArgs.Append($" -s {source}"); - } - - extraArgs = feedArgs.ToString(); - } - + /// The explicit restore sources argument. + private void RestoreProjects(IEnumerable projects, string? explicitRestoreSources, out ConcurrentBag dependencies) + { var successCount = 0; var nugetSourceFailures = 0; ConcurrentBag collectedDependencies = []; @@ -354,7 +352,7 @@ private void RestoreProjects(IEnumerable projects, HashSet? conf foreach (var project in projectGroup) { logger.LogInfo($"Restoring project {project}..."); - var res = dotnet.Restore(new(project, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true, extraArgs, TargetWindows: isWindows)); + var res = dotnet.Restore(new(project, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true, ExtraArgs: explicitRestoreSources, TargetWindows: isWindows)); assets.AddDependenciesRange(res.AssetsFilePaths); lock (sync) { @@ -780,20 +778,20 @@ private HashSet GetExcludedFeeds() /// The set of package feeds to check. /// /// True if all feeds are reachable or false otherwise. - /// Also returns the list of reachable feeds. + /// Also returns the list of reachable feeds as an out parameter. /// - private (bool, List) CheckSpecifiedFeeds(HashSet feeds) + private bool CheckSpecifiedFeeds(HashSet feeds, out List reachableFeeds) { // Exclude any feeds that are configured by the corresponding environment variable. var excludedFeeds = GetExcludedFeeds(); var feedsToCheck = feeds.Where(feed => !excludedFeeds.Contains(feed)).ToHashSet(); - var reachableFeeds = GetReachableNuGetFeeds(feedsToCheck, isFallback: false); + reachableFeeds = GetReachableNuGetFeeds(feedsToCheck, isFallback: false); var allFeedsReachable = reachableFeeds.Count == feedsToCheck.Count; EmitUnreachableFeedsDiagnostics(allFeedsReachable); - return (allFeedsReachable, reachableFeeds); + return allFeedsReachable; } /// @@ -899,13 +897,23 @@ private IEnumerable GetFeeds(Func> getNugetFeeds) logger.LogDebug("No NuGet feeds found in nuget.config files."); } - // todo: this could be improved. - HashSet? allFeeds = null; + // If private package registries are configured for C#, then consider those + // in addition to the ones that are configured in `nuget.config` files. + if (HasPrivateRegistryFeeds) + { + logger.LogInfo($"Found {PrivateRegistryFeeds.Count} private registry feeds configured for C#: {string.Join(", ", PrivateRegistryFeeds.OrderBy(f => f))}"); + explicitFeeds.UnionWith(PrivateRegistryFeeds); + } + + HashSet allFeeds = []; + + // Add all explicitFeeds to the set of all feeds. + allFeeds.UnionWith(explicitFeeds); if (nugetConfigs.Count > 0) { // We don't have to get the feeds from each of the folders from below, it would be enought to check the folders that recursively contain the others. - allFeeds = nugetConfigs + var nugetConfigFeeds = nugetConfigs .Select(config => { try @@ -922,18 +930,13 @@ private IEnumerable GetFeeds(Func> getNugetFeeds) .SelectMany(folder => GetFeeds(() => dotnet.GetNugetFeedsFromFolder(folder!))) .ToHashSet(); - // If we have discovered any explicit feeds, then we also expect these to be in the set of all feeds. - // Normally, it is a safe assumption to make that `GetNugetFeedsFromFolder` will include the feeds configured - // in a NuGet configuration file in the given directory. There is one exception: on a system with case-sensitive - // file systems, we may discover a configuration file such as `Nuget.Config` which is not recognised by `dotnet nuget`. - // In that case, our call to `GetNugetFeeds` will retrieve the feeds from that file (because it is accepted when - // provided explicitly as `--configfile` argument), but the call to `GetNugetFeedsFromFolder` will not. - allFeeds.UnionWith(explicitFeeds); + allFeeds.UnionWith(nugetConfigFeeds); } else { // If we haven't found any `nuget.config` files, then obtain a list of feeds from the root source directory. - allFeeds = GetFeeds(() => dotnet.GetNugetFeedsFromFolder(fileProvider.SourceDir.FullName)).ToHashSet(); + var nugetFeedsFromRoot = GetFeeds(() => dotnet.GetNugetFeedsFromFolder(fileProvider.SourceDir.FullName)); + allFeeds.UnionWith(nugetFeedsFromRoot); } logger.LogInfo($"Found {allFeeds.Count} NuGet feeds (with inherited ones) in nuget.config files: {string.Join(", ", allFeeds.OrderBy(f => f))}"); From 0c371a86adaff7a5fc5255bcbd4918bcdcd7338d Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 9 Apr 2026 15:06:40 +0200 Subject: [PATCH 10/20] C#: Make sure that the feeds that excluded for the feed check (based on an environment variable setting) are still used as sources. --- .../NugetPackageRestorer.cs | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 127c9b04f694..b534a2b697ce 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -20,7 +20,6 @@ internal sealed partial class NugetPackageRestorer : IDisposable { internal const string PublicNugetOrgFeed = "https://api.nuget.org/v3/index.json"; - private readonly bool checkNugetFeedResponsiveness = EnvironmentVariables.GetBooleanOptOut(EnvironmentVariableNames.CheckNugetFeedResponsiveness); private readonly FileProvider fileProvider; private readonly FileContent fileContent; private readonly IDotNet dotnet; @@ -30,6 +29,8 @@ internal sealed partial class NugetPackageRestorer : IDisposable private readonly DependencyDirectory missingPackageDirectory; private readonly ILogger logger; private readonly ICompilationInfoContainer compilationInfoContainer; + private readonly Lazy lazyCheckNugetFeedResponsiveness = new(() => EnvironmentVariables.GetBooleanOptOut(EnvironmentVariableNames.CheckNugetFeedResponsiveness)); + private bool CheckNugetFeedResponsiveness => lazyCheckNugetFeedResponsiveness.Value; private HashSet PrivateRegistryFeeds => dependabotProxy?.RegistryURLs ?? []; private bool HasPrivateRegistryFeeds => PrivateRegistryFeeds.Any(); @@ -113,15 +114,14 @@ public DirectoryInfo[] GetOrderedPackageVersionSubDirectories(string packagePath public HashSet Restore() { var assemblyLookupLocations = new HashSet(); - logger.LogInfo($"Checking NuGet feed responsiveness: {checkNugetFeedResponsiveness}"); - compilationInfoContainer.CompilationInfos.Add(("NuGet feed responsiveness checked", checkNugetFeedResponsiveness ? "1" : "0")); + logger.LogInfo($"Checking NuGet feed responsiveness: {CheckNugetFeedResponsiveness}"); + compilationInfoContainer.CompilationInfos.Add(("NuGet feed responsiveness checked", CheckNugetFeedResponsiveness ? "1" : "0")); HashSet explicitFeeds = []; string? explicitRestoreSources = null; try { - HashSet reachableFeeds = []; HashSet allFeeds = []; // Find feeds that are configured in NuGet.config files and divide them into ones that @@ -129,7 +129,7 @@ public HashSet Restore() // (including inherited ones) from other locations on the host outside of the working directory. (explicitFeeds, allFeeds) = GetAllFeeds(); - if (checkNugetFeedResponsiveness) + if (CheckNugetFeedResponsiveness) { var inheritedFeeds = allFeeds.Except(explicitFeeds).ToHashSet(); @@ -138,11 +138,7 @@ public HashSet Restore() compilationInfoContainer.CompilationInfos.Add(("Inherited NuGet feed count", inheritedFeeds.Count.ToString())); } - var explicitFeedsReachable = CheckSpecifiedFeeds(explicitFeeds, out var reachableExplicitFeeds); - reachableFeeds.UnionWith(reachableExplicitFeeds); - reachableFeeds.UnionWith(GetReachableNuGetFeeds(inheritedFeeds, isFallback: false)); - - if (!explicitFeedsReachable) + if (!CheckSpecifiedFeeds(explicitFeeds, out var reachableFeeds)) { // todo: we could also check the reachability of the inherited nuget feeds, but to use those in the fallback we would need to handle authentication too. var unresponsiveMissingPackageLocation = DownloadMissingPackagesFromSpecificFeeds([], explicitFeeds); @@ -151,6 +147,8 @@ public HashSet Restore() : [unresponsiveMissingPackageLocation]; } + reachableFeeds.UnionWith(GetReachableNuGetFeeds(inheritedFeeds, isFallback: false)); + // If feed responsiveness is being checked, we only want to use the feeds that are reachable (note this set includes private // registry feeds if they are reachable). explicitRestoreSources = MakeRestoreSourcesArgument(reachableFeeds); @@ -216,7 +214,7 @@ public HashSet Restore() var usedPackageNames = GetAllUsedPackageDirNames(dependencies); - var missingPackageLocation = checkNugetFeedResponsiveness + var missingPackageLocation = CheckNugetFeedResponsiveness ? DownloadMissingPackagesFromSpecificFeeds(usedPackageNames, explicitFeeds) : DownloadMissingPackages(usedPackageNames); @@ -233,7 +231,7 @@ public HashSet Restore() /// The feeds to check. /// Whether the feeds are fallback feeds or not. /// The list of feeds that could be reached. - private List GetReachableNuGetFeeds(HashSet feedsToCheck, bool isFallback) + private HashSet GetReachableNuGetFeeds(HashSet feedsToCheck, bool isFallback) { var fallbackStr = isFallback ? "fallback " : ""; logger.LogInfo($"Checking {fallbackStr}NuGet feed reachability on feeds: {string.Join(", ", feedsToCheck.OrderBy(f => f))}"); @@ -241,7 +239,7 @@ private List GetReachableNuGetFeeds(HashSet feedsToCheck, bool i var (initialTimeout, tryCount) = GetFeedRequestSettings(isFallback); var reachableFeeds = feedsToCheck .Where(feed => IsFeedReachable(feed, initialTimeout, tryCount, allowExceptions: false)) - .ToList(); + .ToHashSet(); if (reachableFeeds.Count == 0) { @@ -255,7 +253,7 @@ private List GetReachableNuGetFeeds(HashSet feedsToCheck, bool i return reachableFeeds; } - private List GetReachableFallbackNugetFeeds(HashSet? feedsFromNugetConfigs) + private HashSet GetReachableFallbackNugetFeeds(HashSet? feedsFromNugetConfigs) { var fallbackFeeds = EnvironmentVariables.GetURLs(EnvironmentVariableNames.FallbackNugetFeeds).ToHashSet(); if (fallbackFeeds.Count == 0) @@ -778,19 +776,36 @@ private HashSet GetExcludedFeeds() /// The set of package feeds to check. /// /// True if all feeds are reachable or false otherwise. - /// Also returns the list of reachable feeds as an out parameter. + /// Also returns the set of reachable feeds as an out parameter. /// - private bool CheckSpecifiedFeeds(HashSet feeds, out List reachableFeeds) + private bool CheckSpecifiedFeeds(HashSet feeds, out HashSet reachableFeeds) { - // Exclude any feeds that are configured by the corresponding environment variable. + // Exclude any feeds from the feed check that are configured by the corresponding environment variable. + // These feeds are always assumed to be reachable. var excludedFeeds = GetExcludedFeeds(); - var feedsToCheck = feeds.Where(feed => !excludedFeeds.Contains(feed)).ToHashSet(); + HashSet feedsToCheck = []; + HashSet feedsNotToCheck = []; + foreach (var feed in feeds) + { + if (excludedFeeds.Contains(feed)) + { + logger.LogInfo($"Not checking reachability of NuGet feed '{feed}' as it is in the list of excluded feeds."); + feedsNotToCheck.Add(feed); + } + else + { + feedsToCheck.Add(feed); + } + } + reachableFeeds = GetReachableNuGetFeeds(feedsToCheck, isFallback: false); var allFeedsReachable = reachableFeeds.Count == feedsToCheck.Count; EmitUnreachableFeedsDiagnostics(allFeedsReachable); + reachableFeeds.UnionWith(feedsNotToCheck); + return allFeedsReachable; } From 790835ce2a42dda9a08549cab03b2c067259a93f Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 9 Apr 2026 15:24:37 +0200 Subject: [PATCH 11/20] C#: Remove redundant out parameter from CheckSpecifiedFeeds. --- .../NugetPackageRestorer.cs | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index b534a2b697ce..e508184563f4 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -138,7 +138,7 @@ public HashSet Restore() compilationInfoContainer.CompilationInfos.Add(("Inherited NuGet feed count", inheritedFeeds.Count.ToString())); } - if (!CheckSpecifiedFeeds(explicitFeeds, out var reachableFeeds)) + if (!CheckSpecifiedFeeds(explicitFeeds)) { // todo: we could also check the reachability of the inherited nuget feeds, but to use those in the fallback we would need to handle authentication too. var unresponsiveMissingPackageLocation = DownloadMissingPackagesFromSpecificFeeds([], explicitFeeds); @@ -147,6 +147,9 @@ public HashSet Restore() : [unresponsiveMissingPackageLocation]; } + // All explicit feeds can be considered reachable + HashSet reachableFeeds = []; + reachableFeeds.UnionWith(explicitFeeds); reachableFeeds.UnionWith(GetReachableNuGetFeeds(inheritedFeeds, isFallback: false)); // If feed responsiveness is being checked, we only want to use the feeds that are reachable (note this set includes private @@ -231,7 +234,7 @@ public HashSet Restore() /// The feeds to check. /// Whether the feeds are fallback feeds or not. /// The list of feeds that could be reached. - private HashSet GetReachableNuGetFeeds(HashSet feedsToCheck, bool isFallback) + private List GetReachableNuGetFeeds(HashSet feedsToCheck, bool isFallback) { var fallbackStr = isFallback ? "fallback " : ""; logger.LogInfo($"Checking {fallbackStr}NuGet feed reachability on feeds: {string.Join(", ", feedsToCheck.OrderBy(f => f))}"); @@ -239,7 +242,7 @@ private HashSet GetReachableNuGetFeeds(HashSet feedsToCheck, boo var (initialTimeout, tryCount) = GetFeedRequestSettings(isFallback); var reachableFeeds = feedsToCheck .Where(feed => IsFeedReachable(feed, initialTimeout, tryCount, allowExceptions: false)) - .ToHashSet(); + .ToList(); if (reachableFeeds.Count == 0) { @@ -253,7 +256,7 @@ private HashSet GetReachableNuGetFeeds(HashSet feedsToCheck, boo return reachableFeeds; } - private HashSet GetReachableFallbackNugetFeeds(HashSet? feedsFromNugetConfigs) + private List GetReachableFallbackNugetFeeds(HashSet? feedsFromNugetConfigs) { var fallbackFeeds = EnvironmentVariables.GetURLs(EnvironmentVariableNames.FallbackNugetFeeds).ToHashSet(); if (fallbackFeeds.Count == 0) @@ -775,37 +778,29 @@ private HashSet GetExcludedFeeds() /// /// The set of package feeds to check. /// - /// True if all feeds are reachable or false otherwise. - /// Also returns the set of reachable feeds as an out parameter. + /// True if all feeds are reachable (excluding any feeds that are configured to be excluded from the check) or false otherwise. /// - private bool CheckSpecifiedFeeds(HashSet feeds, out HashSet reachableFeeds) + private bool CheckSpecifiedFeeds(HashSet feeds) { // Exclude any feeds from the feed check that are configured by the corresponding environment variable. // These feeds are always assumed to be reachable. var excludedFeeds = GetExcludedFeeds(); - HashSet feedsToCheck = []; - HashSet feedsNotToCheck = []; - foreach (var feed in feeds) + HashSet feedsToCheck = feeds.Where(feed => { if (excludedFeeds.Contains(feed)) { logger.LogInfo($"Not checking reachability of NuGet feed '{feed}' as it is in the list of excluded feeds."); - feedsNotToCheck.Add(feed); - } - else - { - feedsToCheck.Add(feed); + return false; } - } + return true; + }).ToHashSet(); - reachableFeeds = GetReachableNuGetFeeds(feedsToCheck, isFallback: false); + var reachableFeeds = GetReachableNuGetFeeds(feedsToCheck, isFallback: false); var allFeedsReachable = reachableFeeds.Count == feedsToCheck.Count; EmitUnreachableFeedsDiagnostics(allFeedsReachable); - reachableFeeds.UnionWith(feedsNotToCheck); - return allFeedsReachable; } From 8e698f20bce3007696e0699556416c2f3820988a Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 9 Apr 2026 15:39:43 +0200 Subject: [PATCH 12/20] C#: Address review comments. --- .../NugetPackageRestorer.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index e508184563f4..d2bcd6167ba1 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -122,12 +122,10 @@ public HashSet Restore() try { - HashSet allFeeds = []; - // Find feeds that are configured in NuGet.config files and divide them into ones that // are explicitly configured for the project or by a private registry, and "all feeds" // (including inherited ones) from other locations on the host outside of the working directory. - (explicitFeeds, allFeeds) = GetAllFeeds(); + (explicitFeeds, var allFeeds) = GetAllFeeds(); if (CheckNugetFeedResponsiveness) { From 2a70a9cfd9ab728d299cd5f9d73c9d2712570f16 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 9 Apr 2026 15:49:53 +0200 Subject: [PATCH 13/20] C#: Re-add the compilation information on reachable fallback NuGet feed count. --- .../NugetPackageRestorer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index d2bcd6167ba1..601de70695de 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -274,7 +274,11 @@ private List GetReachableFallbackNugetFeeds(HashSet? feedsFromNu } } - return GetReachableNuGetFeeds(fallbackFeeds, isFallback: true); + var reachableFallbackFeeds = GetReachableNuGetFeeds(fallbackFeeds, isFallback: true); + + compilationInfoContainer.CompilationInfos.Add(("Reachable fallback NuGet feed count", reachableFallbackFeeds.Count.ToString())); + + return reachableFallbackFeeds; } /// From 650e382cca46139b5adc6908a170e01e12706fb8 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 10 Apr 2026 09:02:59 +0200 Subject: [PATCH 14/20] C#: For specific listed nuget feeds in a project, still allow their use unless there is a timeout when trying to reach them. --- .../NugetPackageRestorer.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 601de70695de..f0e8fed7b424 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -148,7 +148,8 @@ public HashSet Restore() // All explicit feeds can be considered reachable HashSet reachableFeeds = []; reachableFeeds.UnionWith(explicitFeeds); - reachableFeeds.UnionWith(GetReachableNuGetFeeds(inheritedFeeds, isFallback: false)); + // Inherited feeds should only be used, if they are indeed reachable (as they may be environment specific). + reachableFeeds.UnionWith(GetReachableNuGetFeeds(inheritedFeeds, isFallback: false, allowNonTimeoutExceptions: false)); // If feed responsiveness is being checked, we only want to use the feeds that are reachable (note this set includes private // registry feeds if they are reachable). @@ -231,15 +232,16 @@ public HashSet Restore() /// /// The feeds to check. /// Whether the feeds are fallback feeds or not. + /// Whether to allow non-timeout exceptions. /// The list of feeds that could be reached. - private List GetReachableNuGetFeeds(HashSet feedsToCheck, bool isFallback) + private List GetReachableNuGetFeeds(HashSet feedsToCheck, bool isFallback, bool allowNonTimeoutExceptions) { var fallbackStr = isFallback ? "fallback " : ""; logger.LogInfo($"Checking {fallbackStr}NuGet feed reachability on feeds: {string.Join(", ", feedsToCheck.OrderBy(f => f))}"); var (initialTimeout, tryCount) = GetFeedRequestSettings(isFallback); var reachableFeeds = feedsToCheck - .Where(feed => IsFeedReachable(feed, initialTimeout, tryCount, allowExceptions: false)) + .Where(feed => IsFeedReachable(feed, initialTimeout, tryCount, allowNonTimeoutExceptions)) .ToList(); if (reachableFeeds.Count == 0) @@ -274,7 +276,7 @@ private List GetReachableFallbackNugetFeeds(HashSet? feedsFromNu } } - var reachableFallbackFeeds = GetReachableNuGetFeeds(fallbackFeeds, isFallback: true); + var reachableFallbackFeeds = GetReachableNuGetFeeds(fallbackFeeds, isFallback: true, allowNonTimeoutExceptions: false); compilationInfoContainer.CompilationInfos.Add(("Reachable fallback NuGet feed count", reachableFallbackFeeds.Count.ToString())); @@ -675,7 +677,7 @@ private static async Task ExecuteGetRequest(string address, HttpClient httpClien } } - private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, bool allowExceptions = true) + private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, bool allowNonTimeoutExceptions) { logger.LogInfo($"Checking if NuGet feed '{feed}' is reachable..."); @@ -730,9 +732,9 @@ private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, } // We're only interested in timeouts. - var start = allowExceptions ? "Considering" : "Not considering"; + var start = allowNonTimeoutExceptions ? "Considering" : "Not considering"; logger.LogInfo($"Querying NuGet feed '{feed}' failed in a timely manner. {start} the feed for use. The reason for the failure: {exc.Message}"); - return allowExceptions; + return allowNonTimeoutExceptions; } } @@ -798,7 +800,7 @@ private bool CheckSpecifiedFeeds(HashSet feeds) return true; }).ToHashSet(); - var reachableFeeds = GetReachableNuGetFeeds(feedsToCheck, isFallback: false); + var reachableFeeds = GetReachableNuGetFeeds(feedsToCheck, isFallback: false, allowNonTimeoutExceptions: true); var allFeedsReachable = reachableFeeds.Count == feedsToCheck.Count; EmitUnreachableFeedsDiagnostics(allFeedsReachable); From dc13650fc9506dd37ca895e672fcb090392330ed Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 10 Apr 2026 10:02:13 +0200 Subject: [PATCH 15/20] C#: Simplify and improve the reachability check and improve the logging. --- .../NugetPackageRestorer.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index f0e8fed7b424..4c196b5d627f 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -666,15 +666,9 @@ private void TryChangeProjectFile(DirectoryInfo projectDir, Regex pattern, strin } } - private static async Task ExecuteGetRequest(string address, HttpClient httpClient, CancellationToken cancellationToken) + private static async Task ExecuteGetRequest(string address, HttpClient httpClient, CancellationToken cancellationToken) { - using var stream = await httpClient.GetStreamAsync(address, cancellationToken); - var buffer = new byte[1024]; - int bytesRead; - while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0) - { - // do nothing - } + return await httpClient.GetAsync(address, cancellationToken); } private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, bool allowNonTimeoutExceptions) @@ -716,7 +710,9 @@ private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, cts.CancelAfter(timeoutMilliSeconds); try { - ExecuteGetRequest(feed, client, cts.Token).GetAwaiter().GetResult(); + logger.LogInfo($"Attempt {i + 1}/{tryCount} to reach NuGet feed '{feed}'."); + var response = ExecuteGetRequest(feed, client, cts.Token).GetAwaiter().GetResult(); + response.EnsureSuccessStatusCode(); logger.LogInfo($"Querying NuGet feed '{feed}' succeeded."); return true; } @@ -731,9 +727,11 @@ private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, continue; } - // We're only interested in timeouts. - var start = allowNonTimeoutExceptions ? "Considering" : "Not considering"; - logger.LogInfo($"Querying NuGet feed '{feed}' failed in a timely manner. {start} the feed for use. The reason for the failure: {exc.Message}"); + // Adjust the message based on whether non-timeout exceptions are allowed. + var useMessage = allowNonTimeoutExceptions + ? "Considering the feed for use despite of the failure as it wasn't a timeout." + : "Not considering the feed for use."; + logger.LogInfo($"Querying NuGet feed '{feed}' failed. {useMessage} The reason for the failure: {exc.Message}"); return allowNonTimeoutExceptions; } } From 05112437c38e2f318e0481010ba02b1c4f0b4c97 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 10 Apr 2026 11:47:12 +0200 Subject: [PATCH 16/20] C#: Rename ExtraArgs to NugetSources. --- .../DotNet.cs | 4 ++-- .../IDotNet.cs | 2 +- .../NugetPackageRestorer.cs | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNet.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNet.cs index 9d3d79e4c4ff..699e06d273c8 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNet.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNet.cs @@ -95,9 +95,9 @@ private string GetRestoreArgs(RestoreSettings restoreSettings) args += " /p:EnableWindowsTargeting=true"; } - if (restoreSettings.ExtraArgs is not null) + if (restoreSettings.NugetSources is not null) { - args += $" {restoreSettings.ExtraArgs}"; + args += $" {restoreSettings.NugetSources}"; } return args; diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IDotNet.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IDotNet.cs index eec6a2b8d3b2..58d4f9b550b9 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IDotNet.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IDotNet.cs @@ -17,7 +17,7 @@ public interface IDotNet IList GetNugetFeedsFromFolder(string folderPath); } - public record class RestoreSettings(string File, string PackageDirectory, bool ForceDotnetRefAssemblyFetching, string? ExtraArgs = null, string? PathToNugetConfig = null, bool ForceReevaluation = false, bool TargetWindows = false); + public record class RestoreSettings(string File, string PackageDirectory, bool ForceDotnetRefAssemblyFetching, string? NugetSources = null, string? PathToNugetConfig = null, bool ForceReevaluation = false, bool TargetWindows = false); public partial record class RestoreResult(bool Success, IList Output) { diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 4c196b5d627f..9c4073e78b7d 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -118,7 +118,7 @@ public HashSet Restore() compilationInfoContainer.CompilationInfos.Add(("NuGet feed responsiveness checked", CheckNugetFeedResponsiveness ? "1" : "0")); HashSet explicitFeeds = []; - string? explicitRestoreSources = null; + string? explicitNugetSources = null; try { @@ -153,13 +153,13 @@ public HashSet Restore() // If feed responsiveness is being checked, we only want to use the feeds that are reachable (note this set includes private // registry feeds if they are reachable). - explicitRestoreSources = MakeRestoreSourcesArgument(reachableFeeds); + explicitNugetSources = MakeRestoreSourcesArgument(reachableFeeds); } else if (HasPrivateRegistryFeeds) { // If private registries are configured they need to be included as sources for the restore, which requires that // they are provided as source arguments for the restore. The private registries are included in the `allFeeds` set. - explicitRestoreSources = MakeRestoreSourcesArgument(allFeeds); + explicitNugetSources = MakeRestoreSourcesArgument(allFeeds); } using (var nuget = new NugetExeWrapper(fileProvider, legacyPackageDirectory, logger)) @@ -202,9 +202,9 @@ public HashSet Restore() } // Restore project dependencies with `dotnet restore`. - var restoredProjects = RestoreSolutions(explicitRestoreSources, out var container); + var restoredProjects = RestoreSolutions(explicitNugetSources, out var container); var projects = fileProvider.Projects.Except(restoredProjects); - RestoreProjects(projects, explicitRestoreSources, out var containers); + RestoreProjects(projects, explicitNugetSources, out var containers); var dependencies = containers.Flatten(container); @@ -291,7 +291,7 @@ private List GetReachableFallbackNugetFeeds(HashSet? feedsFromNu /// Populates dependencies with the relevant dependencies from the assets files generated by the restore. /// Returns a list of projects that are up to date with respect to restore. /// - private IEnumerable RestoreSolutions(string? explicitRestoreSources, out DependencyContainer dependencies) + private IEnumerable RestoreSolutions(string? nugetSources, out DependencyContainer dependencies) { var successCount = 0; var nugetSourceFailures = 0; @@ -302,7 +302,7 @@ private IEnumerable RestoreSolutions(string? explicitRestoreSources, out var projects = fileProvider.Solutions.SelectMany(solution => { logger.LogInfo($"Restoring solution {solution}..."); - var res = dotnet.Restore(new(solution, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true, ExtraArgs: explicitRestoreSources, TargetWindows: isWindows)); + var res = dotnet.Restore(new(solution, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true, NugetSources: nugetSources, TargetWindows: isWindows)); if (res.Success) { successCount++; @@ -357,7 +357,7 @@ private void RestoreProjects(IEnumerable projects, string? explicitResto foreach (var project in projectGroup) { logger.LogInfo($"Restoring project {project}..."); - var res = dotnet.Restore(new(project, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true, ExtraArgs: explicitRestoreSources, TargetWindows: isWindows)); + var res = dotnet.Restore(new(project, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true, NugetSources: explicitRestoreSources, TargetWindows: isWindows)); assets.AddDependenciesRange(res.AssetsFilePaths); lock (sync) { From 0451894bbedec9d89fe676e41a5ab06b837a550b Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 10 Apr 2026 13:11:47 +0200 Subject: [PATCH 17/20] C#: Handle special case when no feeds are reachable. --- .../NugetPackageRestorer.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 9c4073e78b7d..39852bcab2fa 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -27,6 +27,7 @@ internal sealed partial class NugetPackageRestorer : IDisposable private readonly IDiagnosticsWriter diagnosticsWriter; private readonly DependencyDirectory legacyPackageDirectory; private readonly DependencyDirectory missingPackageDirectory; + private readonly DependencyDirectory emptyPackageDirectory; private readonly ILogger logger; private readonly ICompilationInfoContainer compilationInfoContainer; private readonly Lazy lazyCheckNugetFeedResponsiveness = new(() => EnvironmentVariables.GetBooleanOptOut(EnvironmentVariableNames.CheckNugetFeedResponsiveness)); @@ -56,6 +57,7 @@ public NugetPackageRestorer( PackageDirectory = new DependencyDirectory("packages", "package", logger); legacyPackageDirectory = new DependencyDirectory("legacypackages", "legacy package", logger); missingPackageDirectory = new DependencyDirectory("missingpackages", "missing package", logger); + emptyPackageDirectory = new DependencyDirectory("empty", "empty package", logger); } public string? TryRestore(string package) @@ -323,6 +325,12 @@ private IEnumerable RestoreSolutions(string? nugetSources, out Dependenc private string? MakeRestoreSourcesArgument(IEnumerable feeds) { + // If there are no feeds, we want to override any default feeds that `dotnet restore` would use by passing a dummy source argument. + if (!feeds.Any()) + { + return $" -s \"{emptyPackageDirectory.DirInfo.FullName}\""; + } + // Add package sources. If any are present, they override all sources specified in // the configuration file(s). var feedArgs = new StringBuilder(); @@ -973,6 +981,7 @@ public void Dispose() PackageDirectory?.Dispose(); legacyPackageDirectory?.Dispose(); missingPackageDirectory?.Dispose(); + emptyPackageDirectory?.Dispose(); } /// From 01d8b59d99511b3772e2c1b2eefd229dfa0a84dc Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 10 Apr 2026 13:38:03 +0200 Subject: [PATCH 18/20] C#: Only use the default package source when using nuget.exe if it is reachable. --- .../NugetExeWrapper.cs | 4 ++-- .../NugetPackageRestorer.cs | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs index b90b388e865c..e97b0b118c68 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs @@ -33,7 +33,7 @@ internal class NugetExeWrapper : IDisposable /// /// Create the package manager for a specified source tree. /// - public NugetExeWrapper(FileProvider fileProvider, DependencyDirectory packageDirectory, Semmle.Util.Logging.ILogger logger) + public NugetExeWrapper(FileProvider fileProvider, DependencyDirectory packageDirectory, Semmle.Util.Logging.ILogger logger, Func useDefaultFeed) { this.fileProvider = fileProvider; this.packageDirectory = packageDirectory; @@ -43,7 +43,7 @@ public NugetExeWrapper(FileProvider fileProvider, DependencyDirectory packageDir { logger.LogInfo($"Found packages.config files, trying to use nuget.exe for package restore"); nugetExe = ResolveNugetExe(); - if (HasNoPackageSource()) + if (HasNoPackageSource() && useDefaultFeed()) { // We only modify or add a top level nuget.config file nugetConfigPath = Path.Combine(fileProvider.SourceDir.FullName, "nuget.config"); diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 39852bcab2fa..5dc19336572d 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -164,7 +164,7 @@ public HashSet Restore() explicitNugetSources = MakeRestoreSourcesArgument(allFeeds); } - using (var nuget = new NugetExeWrapper(fileProvider, legacyPackageDirectory, logger)) + using (var nuget = new NugetExeWrapper(fileProvider, legacyPackageDirectory, logger, IsDefaultFeedReachable)) { var count = nuget.InstallPackages(); @@ -258,6 +258,12 @@ private List GetReachableNuGetFeeds(HashSet feedsToCheck, bool i return reachableFeeds; } + private bool IsDefaultFeedReachable() + { + var (initialTimeout, tryCount) = GetFeedRequestSettings(isFallback: false); + return IsFeedReachable(PublicNugetOrgFeed, initialTimeout, tryCount, allowNonTimeoutExceptions: false); + } + private List GetReachableFallbackNugetFeeds(HashSet? feedsFromNugetConfigs) { var fallbackFeeds = EnvironmentVariables.GetURLs(EnvironmentVariableNames.FallbackNugetFeeds).ToHashSet(); From 46ebf88e5d9b2f47e568c7330fabb1d8f9dd1fb9 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 10 Apr 2026 13:50:27 +0200 Subject: [PATCH 19/20] C#: Add change-note. --- .../ql/lib/change-notes/2026-04-10-nuget-feed-usage-in-bmn.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 csharp/ql/lib/change-notes/2026-04-10-nuget-feed-usage-in-bmn.md diff --git a/csharp/ql/lib/change-notes/2026-04-10-nuget-feed-usage-in-bmn.md b/csharp/ql/lib/change-notes/2026-04-10-nuget-feed-usage-in-bmn.md new file mode 100644 index 000000000000..6247527b3372 --- /dev/null +++ b/csharp/ql/lib/change-notes/2026-04-10-nuget-feed-usage-in-bmn.md @@ -0,0 +1,4 @@ +--- +category: majorAnalysis +--- +* When resolving dependencies in `build-mode: none`, `dotnet restore` now always receives the NuGet feeds configured in `nuget.config` (if reachable) and any private registries directly, improving reliability when default feeds are unavailable or restricted. From e91693fb5c754e3fcbebc3ed67e44f4a5bd6e02b Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 10 Apr 2026 16:13:12 +0200 Subject: [PATCH 20/20] C#: Only include feeds that we can connect to. --- .../NugetPackageRestorer.cs | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 5dc19336572d..39fabdc3d2ec 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -138,8 +138,9 @@ public HashSet Restore() compilationInfoContainer.CompilationInfos.Add(("Inherited NuGet feed count", inheritedFeeds.Count.ToString())); } - if (!CheckSpecifiedFeeds(explicitFeeds)) + if (!CheckSpecifiedFeeds(explicitFeeds, out var reachableFeeds)) { + // If we experience a timeout, we use this fallback. // todo: we could also check the reachability of the inherited nuget feeds, but to use those in the fallback we would need to handle authentication too. var unresponsiveMissingPackageLocation = DownloadMissingPackagesFromSpecificFeeds([], explicitFeeds); return unresponsiveMissingPackageLocation is null @@ -147,11 +148,8 @@ public HashSet Restore() : [unresponsiveMissingPackageLocation]; } - // All explicit feeds can be considered reachable - HashSet reachableFeeds = []; - reachableFeeds.UnionWith(explicitFeeds); // Inherited feeds should only be used, if they are indeed reachable (as they may be environment specific). - reachableFeeds.UnionWith(GetReachableNuGetFeeds(inheritedFeeds, isFallback: false, allowNonTimeoutExceptions: false)); + reachableFeeds.UnionWith(GetReachableNuGetFeeds(inheritedFeeds, isFallback: false, out var _)); // If feed responsiveness is being checked, we only want to use the feeds that are reachable (note this set includes private // registry feeds if they are reachable). @@ -234,16 +232,22 @@ public HashSet Restore() /// /// The feeds to check. /// Whether the feeds are fallback feeds or not. - /// Whether to allow non-timeout exceptions. + /// Whether a timeout occurred while checking the feeds. /// The list of feeds that could be reached. - private List GetReachableNuGetFeeds(HashSet feedsToCheck, bool isFallback, bool allowNonTimeoutExceptions) + private List GetReachableNuGetFeeds(HashSet feedsToCheck, bool isFallback, out bool isTimeout) { var fallbackStr = isFallback ? "fallback " : ""; logger.LogInfo($"Checking {fallbackStr}NuGet feed reachability on feeds: {string.Join(", ", feedsToCheck.OrderBy(f => f))}"); var (initialTimeout, tryCount) = GetFeedRequestSettings(isFallback); + var timeout = false; var reachableFeeds = feedsToCheck - .Where(feed => IsFeedReachable(feed, initialTimeout, tryCount, allowNonTimeoutExceptions)) + .Where(feed => + { + var reachable = IsFeedReachable(feed, initialTimeout, tryCount, out var feedTimeout); + timeout |= feedTimeout; + return reachable; + }) .ToList(); if (reachableFeeds.Count == 0) @@ -255,13 +259,14 @@ private List GetReachableNuGetFeeds(HashSet feedsToCheck, bool i logger.LogInfo($"Reachable {fallbackStr}NuGet feeds: {string.Join(", ", reachableFeeds.OrderBy(f => f))}"); } + isTimeout = timeout; return reachableFeeds; } private bool IsDefaultFeedReachable() { var (initialTimeout, tryCount) = GetFeedRequestSettings(isFallback: false); - return IsFeedReachable(PublicNugetOrgFeed, initialTimeout, tryCount, allowNonTimeoutExceptions: false); + return IsFeedReachable(PublicNugetOrgFeed, initialTimeout, tryCount, out var _); } private List GetReachableFallbackNugetFeeds(HashSet? feedsFromNugetConfigs) @@ -284,7 +289,7 @@ private List GetReachableFallbackNugetFeeds(HashSet? feedsFromNu } } - var reachableFallbackFeeds = GetReachableNuGetFeeds(fallbackFeeds, isFallback: true, allowNonTimeoutExceptions: false); + var reachableFallbackFeeds = GetReachableNuGetFeeds(fallbackFeeds, isFallback: true, out var _); compilationInfoContainer.CompilationInfos.Add(("Reachable fallback NuGet feed count", reachableFallbackFeeds.Count.ToString())); @@ -685,7 +690,7 @@ private static async Task ExecuteGetRequest(string address, return await httpClient.GetAsync(address, cancellationToken); } - private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, bool allowNonTimeoutExceptions) + private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, out bool isTimeout) { logger.LogInfo($"Checking if NuGet feed '{feed}' is reachable..."); @@ -718,6 +723,8 @@ private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, using HttpClient client = new(httpClientHandler); + isTimeout = false; + for (var i = 0; i < tryCount; i++) { using var cts = new CancellationTokenSource(); @@ -741,16 +748,13 @@ private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, continue; } - // Adjust the message based on whether non-timeout exceptions are allowed. - var useMessage = allowNonTimeoutExceptions - ? "Considering the feed for use despite of the failure as it wasn't a timeout." - : "Not considering the feed for use."; - logger.LogInfo($"Querying NuGet feed '{feed}' failed. {useMessage} The reason for the failure: {exc.Message}"); - return allowNonTimeoutExceptions; + logger.LogInfo($"Querying NuGet feed '{feed}' failed. The reason for the failure: {exc.Message}"); + return false; } } logger.LogWarning($"Didn't receive answer from NuGet feed '{feed}'. Tried it {tryCount} times."); + isTimeout = true; return false; } @@ -793,10 +797,12 @@ private HashSet GetExcludedFeeds() /// Checks that we can connect to the specified NuGet feeds. /// /// The set of package feeds to check. + /// The list of feeds that were reachable. /// - /// True if all feeds are reachable (excluding any feeds that are configured to be excluded from the check) or false otherwise. + /// True if there is no timeout when trying to reach the feeds (excluding any feeds that are configured + /// to be excluded from the check) or false otherwise. /// - private bool CheckSpecifiedFeeds(HashSet feeds) + private bool CheckSpecifiedFeeds(HashSet feeds, out HashSet reachableFeeds) { // Exclude any feeds from the feed check that are configured by the corresponding environment variable. // These feeds are always assumed to be reachable. @@ -812,12 +818,11 @@ private bool CheckSpecifiedFeeds(HashSet feeds) return true; }).ToHashSet(); - var reachableFeeds = GetReachableNuGetFeeds(feedsToCheck, isFallback: false, allowNonTimeoutExceptions: true); - var allFeedsReachable = reachableFeeds.Count == feedsToCheck.Count; - - EmitUnreachableFeedsDiagnostics(allFeedsReachable); + reachableFeeds = GetReachableNuGetFeeds(feedsToCheck, isFallback: false, out var isTimeout).ToHashSet(); - return allFeedsReachable; + var noTimeout = !isTimeout; + EmitUnreachableFeedsDiagnostics(noTimeout); + return noTimeout; } ///