From f338ded34947c9f9d617b1e577d9fed527dea6a6 Mon Sep 17 00:00:00 2001 From: MarkLee131 Date: Sat, 4 Apr 2026 20:57:13 +0800 Subject: [PATCH 01/38] Java: treat hash/encrypt/digest methods as sensitive-log sanitizers The sensitive-log query (CWE-532) lacked sanitizers for hashed or encrypted data, while the sibling cleartext-storage query (CWE-312) already recognized methods with "encrypt", "hash", or "digest" in their names as sanitizers (CleartextStorageQuery.qll:86). This adds an EncryptionBarrier to SensitiveLoggingQuery that applies the same name-based heuristic, making the two queries consistent. Calls like DigestUtils.sha256Hex(password) or hashPassword(secret) are no longer flagged when their results are logged. --- .../2026-04-04-sensitive-log-hash-sanitizer.md | 4 ++++ .../code/java/security/SensitiveLoggingQuery.qll | 13 +++++++++++++ .../security/CWE-532/SensitiveLogInfo.expected | 4 ++++ java/ql/test/query-tests/security/CWE-532/Test.java | 13 +++++++++++++ 4 files changed, 34 insertions(+) create mode 100644 java/ql/lib/change-notes/2026-04-04-sensitive-log-hash-sanitizer.md diff --git a/java/ql/lib/change-notes/2026-04-04-sensitive-log-hash-sanitizer.md b/java/ql/lib/change-notes/2026-04-04-sensitive-log-hash-sanitizer.md new file mode 100644 index 00000000000..7323ab09737 --- /dev/null +++ b/java/ql/lib/change-notes/2026-04-04-sensitive-log-hash-sanitizer.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The `java/sensitive-log` query now treats method calls whose names contain "encrypt", "hash", or "digest" as sanitizers, consistent with the existing treatment in `java/cleartext-storage-in-log`. This reduces false positives when sensitive data is hashed or encrypted before logging. diff --git a/java/ql/lib/semmle/code/java/security/SensitiveLoggingQuery.qll b/java/ql/lib/semmle/code/java/security/SensitiveLoggingQuery.qll index 7058b844cbd..5f11ae0d214 100644 --- a/java/ql/lib/semmle/code/java/security/SensitiveLoggingQuery.qll +++ b/java/ql/lib/semmle/code/java/security/SensitiveLoggingQuery.qll @@ -120,6 +120,19 @@ private class DefaultSensitiveLoggerBarrier extends SensitiveLoggerBarrier { } } +/** + * A barrier for sensitive data that has been hashed, encrypted, or digested before logging. + * This is consistent with the treatment of encryption in `CleartextStorageQuery.qll` (CWE-312). + */ +private class EncryptionBarrier extends SensitiveLoggerBarrier { + EncryptionBarrier() { + exists(MethodCall mc | + this.asExpr() = mc and + mc.getMethod().getName().toLowerCase().matches(["%encrypt%", "%hash%", "%digest%"]) + ) + } +} + /** A data-flow configuration for identifying potentially-sensitive data flowing to a log output. */ module SensitiveLoggerConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node source) { source instanceof SensitiveLoggerSource } diff --git a/java/ql/test/query-tests/security/CWE-532/SensitiveLogInfo.expected b/java/ql/test/query-tests/security/CWE-532/SensitiveLogInfo.expected index 54f1e9f8a5a..af315238bbf 100644 --- a/java/ql/test/query-tests/security/CWE-532/SensitiveLogInfo.expected +++ b/java/ql/test/query-tests/security/CWE-532/SensitiveLogInfo.expected @@ -3,6 +3,7 @@ | Test.java:12:22:12:52 | ... + ... | Test.java:12:44:12:52 | authToken : String | Test.java:12:22:12:52 | ... + ... | This $@ is written to a log file. | Test.java:12:44:12:52 | authToken | potentially sensitive information | | Test.java:21:22:21:75 | ... + ... | Test.java:21:44:21:52 | authToken : String | Test.java:21:22:21:75 | ... + ... | This $@ is written to a log file. | Test.java:21:44:21:52 | authToken | potentially sensitive information | | Test.java:22:22:22:75 | ... + ... | Test.java:22:44:22:52 | authToken : String | Test.java:22:22:22:75 | ... + ... | This $@ is written to a log file. | Test.java:22:44:22:52 | authToken | potentially sensitive information | +| Test.java:31:21:31:37 | ... + ... | Test.java:31:30:31:37 | password : String | Test.java:31:21:31:37 | ... + ... | This $@ is written to a log file. | Test.java:31:30:31:37 | password | potentially sensitive information | edges | Test.java:11:46:11:53 | password : String | Test.java:11:21:11:53 | ... + ... | provenance | Sink:MaD:2 | | Test.java:12:44:12:52 | authToken : String | Test.java:12:22:12:52 | ... + ... | provenance | Sink:MaD:1 | @@ -10,6 +11,7 @@ edges | Test.java:21:44:21:67 | substring(...) : String | Test.java:21:22:21:75 | ... + ... | provenance | Sink:MaD:1 | | Test.java:22:44:22:52 | authToken : String | Test.java:22:44:22:67 | substring(...) : String | provenance | MaD:3 | | Test.java:22:44:22:67 | substring(...) : String | Test.java:22:22:22:75 | ... + ... | provenance | Sink:MaD:1 | +| Test.java:31:30:31:37 | password : String | Test.java:31:21:31:37 | ... + ... | provenance | Sink:MaD:2 | models | 1 | Sink: org.apache.logging.log4j; Logger; true; error; (String); ; Argument[0]; log-injection; manual | | 2 | Sink: org.apache.logging.log4j; Logger; true; info; (String); ; Argument[0]; log-injection; manual | @@ -25,4 +27,6 @@ nodes | Test.java:22:22:22:75 | ... + ... | semmle.label | ... + ... | | Test.java:22:44:22:52 | authToken : String | semmle.label | authToken : String | | Test.java:22:44:22:67 | substring(...) : String | semmle.label | substring(...) : String | +| Test.java:31:21:31:37 | ... + ... | semmle.label | ... + ... | +| Test.java:31:30:31:37 | password : String | semmle.label | password : String | subpaths diff --git a/java/ql/test/query-tests/security/CWE-532/Test.java b/java/ql/test/query-tests/security/CWE-532/Test.java index 6521f7e2df7..a25becb89bb 100644 --- a/java/ql/test/query-tests/security/CWE-532/Test.java +++ b/java/ql/test/query-tests/security/CWE-532/Test.java @@ -21,4 +21,17 @@ class Test { logger.error("Auth failed for: " + authToken.substring(1,5) + "..."); // $ Alert logger.error("Auth failed for: " + authToken.substring(0,8) + "..."); // $ Alert } + + // Tests for hash/encryption sanitizer + void testHashSanitizer(String password, String authToken) { + Logger logger = null; + logger.info("hash: " + hashPassword(password)); // Safe - hashed + logger.info("hash: " + sha256Digest(authToken)); // Safe - digested + logger.info("enc: " + encryptValue(password)); // Safe - encrypted + logger.info("pw: " + password); // $ Alert - not hashed + } + + static String hashPassword(String input) { return input; } + static String sha256Digest(String input) { return input; } + static String encryptValue(String input) { return input; } } From d22381a9436626b548696208d06d1f2a8d58104f Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 27 Feb 2026 14:17:29 +0000 Subject: [PATCH 02/38] 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 1d01412ee05..33e8db19eab 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -192,6 +192,34 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching } } - 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 439e37a1984febc49d63e9eb8d900e48d126f8db Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 27 Feb 2026 14:23:03 +0000 Subject: [PATCH 03/38] 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 33e8db19eab..5d7ad8f6124 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -767,8 +767,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching /// 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 8215737db907034b3784aa80737bbf0cd56dc2ed Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 27 Feb 2026 14:24:55 +0000 Subject: [PATCH 04/38] 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 5d7ad8f6124..7bdec255ab4 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -119,13 +119,37 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 fdbaba896f47421c457e5a42b9c02d65e5a9ad02 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 27 Feb 2026 14:25:58 +0000 Subject: [PATCH 05/38] 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 7bdec255ab4..4a4029507d1 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -127,14 +127,11 @@ namespace Semmle.Extraction.CSharp.DependencyFetching (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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 9898e21ce76b43235ede568124b1e62c5ac6b555 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 27 Feb 2026 14:30:57 +0000 Subject: [PATCH 06/38] 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 4a4029507d1..f300865d2ae 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -755,13 +755,10 @@ namespace Semmle.Extraction.CSharp.DependencyFetching } /// - /// 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching )); } compilationInfoContainer.CompilationInfos.Add(("All NuGet feeds reachable", allFeedsReachable ? "1" : "0")); - - return allFeedsReachable; } private IEnumerable GetFeeds(Func> getNugetFeeds) From 17c45fcd75d847d8be6f0941280bb348f5e17699 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 27 Feb 2026 14:32:01 +0000 Subject: [PATCH 07/38] 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 f300865d2ae..474b3f6c7f2 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -132,10 +132,10 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 cce5f06086b19cc489ce3c804f30b83d305ce125 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 27 Feb 2026 14:38:01 +0000 Subject: [PATCH 08/38] 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 474b3f6c7f2..0e8134bd19b 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -116,6 +116,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching HashSet? explicitFeeds = null; HashSet? allFeeds = null; + HashSet? reachableFeeds = []; try { @@ -131,8 +132,11 @@ namespace Semmle.Extraction.CSharp.DependencyFetching // 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching // 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching /// 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching this.EmitUnreachableFeedsDiagnostics(allFeedsReachable); - return allFeedsReachable; + return (allFeedsReachable, reachableFeeds); } /// From 132dc1fa26ffb797a99a56fd6f2cc3adb3fa4701 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 9 Apr 2026 13:06:32 +0200 Subject: [PATCH 09/38] 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 0e8134bd19b..9ccf550a8aa 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -20,6 +20,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching { 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching // 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching } // 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching // `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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching // 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 365b419b5e19077f22a2063a68aa0c5c07c46bf8 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 9 Apr 2026 14:32:30 +0200 Subject: [PATCH 10/38] 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 | 129 +++++++++--------- 1 file changed, 66 insertions(+), 63 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 9ccf550a8aa..127c9b04f69 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -30,6 +30,8 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching ? [] : [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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching } // 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching /// 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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) + /// The explicit restore sources argument. + private void RestoreProjects(IEnumerable projects, string? explicitRestoreSources, 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(); - } - var successCount = 0; var nugetSourceFailures = 0; ConcurrentBag collectedDependencies = []; @@ -354,7 +352,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching /// 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching .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 4dad62c481cce5b966c8480e19b06d8e93d25cf6 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 9 Apr 2026 15:06:40 +0200 Subject: [PATCH 11/38] 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 127c9b04f69..b534a2b697c 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -20,7 +20,6 @@ namespace Semmle.Extraction.CSharp.DependencyFetching { 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching // (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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching : [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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching var usedPackageNames = GetAllUsedPackageDirNames(dependencies); - var missingPackageLocation = checkNugetFeedResponsiveness + var missingPackageLocation = CheckNugetFeedResponsiveness ? DownloadMissingPackagesFromSpecificFeeds(usedPackageNames, explicitFeeds) : DownloadMissingPackages(usedPackageNames); @@ -233,7 +231,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching /// 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching /// 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 c53b2f589b137a3c7674644dac0afba6c3e3c5f4 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 9 Apr 2026 15:24:37 +0200 Subject: [PATCH 12/38] 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 b534a2b697c..e508184563f 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -138,7 +138,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching : [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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching /// 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching /// /// 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); + return false; } - else - { - feedsToCheck.Add(feed); - } - } + 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 b95a8aa3786c2d8cb217a90a1ee95dd0439decef Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 9 Apr 2026 15:39:43 +0200 Subject: [PATCH 13/38] 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 e508184563f..d2bcd6167ba 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -122,12 +122,10 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 21fb44d0bad7680dfb0b91255cb83e35a6e8faa7 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 9 Apr 2026 15:49:53 +0200 Subject: [PATCH 14/38] 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 d2bcd6167ba..601de70695d 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -274,7 +274,11 @@ namespace Semmle.Extraction.CSharp.DependencyFetching } } - 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 1dfe30deaf3fd1f0574795d344e8f500179272d8 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 10 Apr 2026 09:02:59 +0200 Subject: [PATCH 15/38] 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 601de70695d..f0e8fed7b42 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -148,7 +148,8 @@ namespace Semmle.Extraction.CSharp.DependencyFetching // 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching /// /// 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching } } - 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching } } - 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching } // 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 8369c926b1886cca760ceb66af36e6e98bd9ae0f Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 10 Apr 2026 10:02:13 +0200 Subject: [PATCH 16/38] 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 f0e8fed7b42..4c196b5d627 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -666,15 +666,9 @@ namespace Semmle.Extraction.CSharp.DependencyFetching } } - 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 1ee6d631c6614aa4b33f81800c625175b2f415bf Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 10 Apr 2026 11:47:12 +0200 Subject: [PATCH 17/38] 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 9d3d79e4c4f..699e06d273c 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNet.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNet.cs @@ -95,9 +95,9 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 eec6a2b8d3b..58d4f9b550b 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IDotNet.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IDotNet.cs @@ -17,7 +17,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 4c196b5d627..9c4073e78b7 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -118,7 +118,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching compilationInfoContainer.CompilationInfos.Add(("NuGet feed responsiveness checked", CheckNugetFeedResponsiveness ? "1" : "0")); HashSet explicitFeeds = []; - string? explicitRestoreSources = null; + string? explicitNugetSources = null; try { @@ -153,13 +153,13 @@ namespace Semmle.Extraction.CSharp.DependencyFetching // 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching } // 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching /// 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 e6df1d8d8ac7ace329549df346b5e47f9a2a6f89 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 10 Apr 2026 13:11:47 +0200 Subject: [PATCH 18/38] 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 9c4073e78b7..39852bcab2f 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -27,6 +27,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching PackageDirectory?.Dispose(); legacyPackageDirectory?.Dispose(); missingPackageDirectory?.Dispose(); + emptyPackageDirectory?.Dispose(); } /// From c0a1dd0524f90b2f29e767a0157471784c4ad9ab Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 10 Apr 2026 13:38:03 +0200 Subject: [PATCH 19/38] C#: Only use the default package source when using nuget.exe if it is reachable. --- .../NugetExeWrapper.cs | 4 ++-- .../NugetPackageRestorer.cs | 13 ++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs index b90b388e865..e97b0b118c6 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs @@ -33,7 +33,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching /// /// 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching { 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 39852bcab2f..d3a8020543c 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -164,7 +164,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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,17 @@ namespace Semmle.Extraction.CSharp.DependencyFetching return reachableFeeds; } + private bool IsDefaultFeedReachable() + { + if (CheckNugetFeedResponsiveness) + { + var (initialTimeout, tryCount) = GetFeedRequestSettings(isFallback: false); + return IsFeedReachable(PublicNugetOrgFeed, initialTimeout, tryCount, allowNonTimeoutExceptions: false); + } + + return true; + } + private List GetReachableFallbackNugetFeeds(HashSet? feedsFromNugetConfigs) { var fallbackFeeds = EnvironmentVariables.GetURLs(EnvironmentVariableNames.FallbackNugetFeeds).ToHashSet(); From 8372a37f74206ca8f4a022d9873e783d9418e043 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 10 Apr 2026 16:13:12 +0200 Subject: [PATCH 20/38] 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 d3a8020543c..e6eb7320e34 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -138,8 +138,9 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching : [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 @@ namespace Semmle.Extraction.CSharp.DependencyFetching /// /// 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,6 +259,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching logger.LogInfo($"Reachable {fallbackStr}NuGet feeds: {string.Join(", ", reachableFeeds.OrderBy(f => f))}"); } + isTimeout = timeout; return reachableFeeds; } @@ -263,7 +268,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching if (CheckNugetFeedResponsiveness) { var (initialTimeout, tryCount) = GetFeedRequestSettings(isFallback: false); - return IsFeedReachable(PublicNugetOrgFeed, initialTimeout, tryCount, allowNonTimeoutExceptions: false); + return IsFeedReachable(PublicNugetOrgFeed, initialTimeout, tryCount, out var _); } return true; @@ -289,7 +294,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching } } - 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())); @@ -690,7 +695,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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..."); @@ -723,6 +728,8 @@ namespace Semmle.Extraction.CSharp.DependencyFetching using HttpClient client = new(httpClientHandler); + isTimeout = false; + for (var i = 0; i < tryCount; i++) { using var cts = new CancellationTokenSource(); @@ -746,16 +753,13 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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; } @@ -798,10 +802,12 @@ namespace Semmle.Extraction.CSharp.DependencyFetching /// 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. @@ -817,12 +823,11 @@ namespace Semmle.Extraction.CSharp.DependencyFetching return true; }).ToHashSet(); - var reachableFeeds = GetReachableNuGetFeeds(feedsToCheck, isFallback: false, allowNonTimeoutExceptions: true); - var allFeedsReachable = reachableFeeds.Count == feedsToCheck.Count; + reachableFeeds = GetReachableNuGetFeeds(feedsToCheck, isFallback: false, out var isTimeout).ToHashSet(); - EmitUnreachableFeedsDiagnostics(allFeedsReachable); - - return allFeedsReachable; + var noTimeout = !isTimeout; + EmitUnreachableFeedsDiagnostics(noTimeout); + return noTimeout; } /// From 6f888f1544903ceea24e579c18da691f472fee82 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Mon, 13 Apr 2026 11:00:48 +0200 Subject: [PATCH 21/38] C#: Change the All NuGet feed reachable telemetry. --- .../NugetPackageRestorer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index e6eb7320e34..0888cf9d158 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -825,9 +825,9 @@ namespace Semmle.Extraction.CSharp.DependencyFetching reachableFeeds = GetReachableNuGetFeeds(feedsToCheck, isFallback: false, out var isTimeout).ToHashSet(); - var noTimeout = !isTimeout; - EmitUnreachableFeedsDiagnostics(noTimeout); - return noTimeout; + var allReachable = reachableFeeds.Count == feedsToCheck.Count; + EmitUnreachableFeedsDiagnostics(allReachable); + return !isTimeout; } /// From 597f3fa727f2437c1c820fcd5fda7a153c26f48e Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Mon, 13 Apr 2026 11:25:05 +0200 Subject: [PATCH 22/38] C#: Update integration test expected output. --- .../CompilationInfo.expected | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_error/CompilationInfo.expected b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_error/CompilationInfo.expected index 3a74bcbd56e..4cd316ce1e8 100644 --- a/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_error/CompilationInfo.expected +++ b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_error/CompilationInfo.expected @@ -1,5 +1,5 @@ -| All NuGet feeds reachable | 1.0 | -| Failed project restore with package source error | 1.0 | +| All NuGet feeds reachable | 0.0 | +| Failed project restore with package source error | 0.0 | | Failed solution restore with package source error | 0.0 | | Fallback nuget restore | 1.0 | | NuGet feed responsiveness checked | 1.0 | From b7e3e6c5ca8e3e4d3cd82f94f305c3150b396275 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 10 Apr 2026 13:50:27 +0200 Subject: [PATCH 23/38] 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 00000000000..6247527b337 --- /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 ca0c2746fc8ea2f81e43e5f55c3011ac9ccbbcd3 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Mon, 13 Apr 2026 13:07:59 +0200 Subject: [PATCH 24/38] C#: Address Copilots review comments. --- .../NugetPackageRestorer.cs | 11 +++++++---- .../2026-04-10-nuget-feed-usage-in-bmn.md | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 0888cf9d158..bfd4abfa848 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -352,7 +352,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching var feedArgs = new StringBuilder(); foreach (var feed in feeds) { - feedArgs.Append($" -s {feed}"); + feedArgs.Append($" -s \"{feed}\""); } return feedArgs.ToString(); @@ -692,7 +692,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching private static async Task ExecuteGetRequest(string address, HttpClient httpClient, CancellationToken cancellationToken) { - return await httpClient.GetAsync(address, cancellationToken); + return await httpClient.GetAsync(address, HttpCompletionOption.ResponseHeadersRead, cancellationToken); } private bool IsFeedReachable(string feed, int timeoutMilliSeconds, int tryCount, out bool isTimeout) @@ -737,7 +737,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching try { logger.LogInfo($"Attempt {i + 1}/{tryCount} to reach NuGet feed '{feed}'."); - var response = ExecuteGetRequest(feed, client, cts.Token).GetAwaiter().GetResult(); + using var response = ExecuteGetRequest(feed, client, cts.Token).GetAwaiter().GetResult(); response.EnsureSuccessStatusCode(); logger.LogInfo($"Querying NuGet feed '{feed}' succeeded."); return true; @@ -824,9 +824,12 @@ namespace Semmle.Extraction.CSharp.DependencyFetching }).ToHashSet(); reachableFeeds = GetReachableNuGetFeeds(feedsToCheck, isFallback: false, out var isTimeout).ToHashSet(); - var allReachable = reachableFeeds.Count == feedsToCheck.Count; EmitUnreachableFeedsDiagnostics(allReachable); + + // Always consider feeds excluded for the reachability check as reachable. + reachableFeeds.UnionWith(feeds.Where(feed => excludedFeeds.Contains(feed))); + return !isTimeout; } 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 index 6247527b337..a4282d0468d 100644 --- 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 @@ -1,4 +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. +* When resolving dependencies in `build-mode: none`, `dotnet restore` now explicitly receives reachable NuGet feeds configured in `nuget.config` when feed responsiveness checking is enabled (the default), and any private registries directly, improving reliability when default feeds are unavailable or restricted. From 5ff4b437321b2350e48d28ab172571525ea9ec78 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 22 Apr 2026 10:21:10 +0200 Subject: [PATCH 25/38] C#: Address review comment. --- .../NugetPackageRestorer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index bfd4abfa848..9135587abb4 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -30,8 +30,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching private readonly DependencyDirectory emptyPackageDirectory; private readonly ILogger logger; private readonly ICompilationInfoContainer compilationInfoContainer; - private readonly Lazy lazyCheckNugetFeedResponsiveness = new(() => EnvironmentVariables.GetBooleanOptOut(EnvironmentVariableNames.CheckNugetFeedResponsiveness)); - private bool CheckNugetFeedResponsiveness => lazyCheckNugetFeedResponsiveness.Value; + private bool CheckNugetFeedResponsiveness { get; } = EnvironmentVariables.GetBooleanOptOut(EnvironmentVariableNames.CheckNugetFeedResponsiveness); private HashSet PrivateRegistryFeeds => dependabotProxy?.RegistryURLs ?? []; private bool HasPrivateRegistryFeeds => PrivateRegistryFeeds.Any(); From 9bd4f654631f4365afec90db470d0341b31818d8 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 22 Apr 2026 10:29:42 +0200 Subject: [PATCH 26/38] C#: Also apply feed exclusions to inherited feeds. --- .../NugetPackageRestorer.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 9135587abb4..3c3b00907f8 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -137,7 +137,11 @@ namespace Semmle.Extraction.CSharp.DependencyFetching compilationInfoContainer.CompilationInfos.Add(("Inherited NuGet feed count", inheritedFeeds.Count.ToString())); } - if (!CheckSpecifiedFeeds(explicitFeeds, out var reachableFeeds)) + var timeout = CheckSpecifiedFeeds(explicitFeeds, out var reachableFeeds); + var allReachable = explicitFeeds.Count == reachableFeeds.Count; + EmitUnreachableFeedsDiagnostics(allReachable); + + if (timeout) { // 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. @@ -148,7 +152,8 @@ namespace Semmle.Extraction.CSharp.DependencyFetching } // Inherited feeds should only be used, if they are indeed reachable (as they may be environment specific). - reachableFeeds.UnionWith(GetReachableNuGetFeeds(inheritedFeeds, isFallback: false, out var _)); + CheckSpecifiedFeeds(inheritedFeeds, out var reachableInheritedFeeds); + reachableFeeds.UnionWith(reachableInheritedFeeds); // 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). @@ -803,7 +808,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching /// The set of package feeds to check. /// The list of feeds that were reachable. /// - /// True if there is no timeout when trying to reach the feeds (excluding any feeds that are configured + /// True if there is a 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, out HashSet reachableFeeds) @@ -823,13 +828,11 @@ namespace Semmle.Extraction.CSharp.DependencyFetching }).ToHashSet(); reachableFeeds = GetReachableNuGetFeeds(feedsToCheck, isFallback: false, out var isTimeout).ToHashSet(); - var allReachable = reachableFeeds.Count == feedsToCheck.Count; - EmitUnreachableFeedsDiagnostics(allReachable); // Always consider feeds excluded for the reachability check as reachable. reachableFeeds.UnionWith(feeds.Where(feed => excludedFeeds.Contains(feed))); - return !isTimeout; + return isTimeout; } /// From 831b4d6cebf960a10a2cfe288f5ca13c8476a4ae Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 22 Apr 2026 10:54:25 +0200 Subject: [PATCH 27/38] C#: Add NuGet package missing failures to the compilation info. --- .../IDotNet.cs | 3 +++ .../NugetPackageRestorer.cs | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IDotNet.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IDotNet.cs index 58d4f9b550b..d14dee50652 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IDotNet.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IDotNet.cs @@ -33,6 +33,9 @@ namespace Semmle.Extraction.CSharp.DependencyFetching private readonly Lazy hasNugetNoStablePackageVersionError = new(() => Output.Any(s => s.Contains("NU1103"))); public bool HasNugetNoStablePackageVersionError => hasNugetNoStablePackageVersionError.Value; + private readonly Lazy hasNugetPackageMissingError = new(() => Output.Any(s => s.Contains("NU1101"))); + public bool HasNugetPackageMissingError => hasNugetPackageMissingError.Value; + private static IEnumerable GetFirstGroupOnMatch(Regex regex, IEnumerable lines) => lines .Select(line => regex.Match(line)) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 3c3b00907f8..ab4fd27a7e7 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -317,6 +317,8 @@ namespace Semmle.Extraction.CSharp.DependencyFetching { var successCount = 0; var nugetSourceFailures = 0; + var nugetMissingPackageFailures = 0; + var assets = new Assets(logger); var isWindows = fileContent.UseWindowsForms || fileContent.UseWpf; @@ -333,12 +335,17 @@ namespace Semmle.Extraction.CSharp.DependencyFetching { nugetSourceFailures++; } + if (res.HasNugetPackageMissingError) + { + nugetMissingPackageFailures++; + } assets.AddDependenciesRange(res.AssetsFilePaths); return res.RestoredProjects; }).ToList(); dependencies = assets.Dependencies; compilationInfoContainer.CompilationInfos.Add(("Successfully restored solution files", successCount.ToString())); compilationInfoContainer.CompilationInfos.Add(("Failed solution restore with package source error", nugetSourceFailures.ToString())); + compilationInfoContainer.CompilationInfos.Add(("Failed solution restore with missing package error", nugetMissingPackageFailures.ToString())); compilationInfoContainer.CompilationInfos.Add(("Restored projects through solution files", projects.Count.ToString())); return projects; } @@ -373,6 +380,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching { var successCount = 0; var nugetSourceFailures = 0; + var nugetMissingPackageFailures = 0; ConcurrentBag collectedDependencies = []; var isWindows = fileContent.UseWindowsForms || fileContent.UseWpf; @@ -397,6 +405,10 @@ namespace Semmle.Extraction.CSharp.DependencyFetching { nugetSourceFailures++; } + if (res.HasNugetPackageMissingError) + { + nugetMissingPackageFailures++; + } } } collectedDependencies.Add(assets.Dependencies); @@ -404,6 +416,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching dependencies = collectedDependencies; compilationInfoContainer.CompilationInfos.Add(("Successfully restored project files", successCount.ToString())); compilationInfoContainer.CompilationInfos.Add(("Failed project restore with package source error", nugetSourceFailures.ToString())); + compilationInfoContainer.CompilationInfos.Add(("Failed project restore with missing package error", nugetMissingPackageFailures.ToString())); } private AssemblyLookupLocation? DownloadMissingPackagesFromSpecificFeeds(IEnumerable usedPackageNames, HashSet? feedsFromNugetConfigs) From a6d1ccae8e4e94bb7aab52bd0f9e237a4a3b1f3d Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 22 Apr 2026 11:36:55 +0200 Subject: [PATCH 28/38] C#: Update integration test expected output. --- .../all-platforms/standalone_resx/CompilationInfo.expected | 2 ++ .../all-platforms/standalone_slnx/CompilationInfo.expected | 2 ++ .../all-platforms/standalone_winforms/CompilationInfo.expected | 2 ++ .../CompilationInfo.expected | 2 ++ 4 files changed, 8 insertions(+) diff --git a/csharp/ql/integration-tests/all-platforms/standalone_resx/CompilationInfo.expected b/csharp/ql/integration-tests/all-platforms/standalone_resx/CompilationInfo.expected index dfab1016a6b..6d91b270022 100644 --- a/csharp/ql/integration-tests/all-platforms/standalone_resx/CompilationInfo.expected +++ b/csharp/ql/integration-tests/all-platforms/standalone_resx/CompilationInfo.expected @@ -1,5 +1,7 @@ | All NuGet feeds reachable | 1.0 | +| Failed project restore with missing package error | 0.0 | | Failed project restore with package source error | 0.0 | +| Failed solution restore with missing package error | 0.0 | | Failed solution restore with package source error | 0.0 | | Inherited NuGet feed count | 1.0 | | NuGet feed responsiveness checked | 1.0 | diff --git a/csharp/ql/integration-tests/all-platforms/standalone_slnx/CompilationInfo.expected b/csharp/ql/integration-tests/all-platforms/standalone_slnx/CompilationInfo.expected index 3bd3941b27c..82cf0509d34 100644 --- a/csharp/ql/integration-tests/all-platforms/standalone_slnx/CompilationInfo.expected +++ b/csharp/ql/integration-tests/all-platforms/standalone_slnx/CompilationInfo.expected @@ -1,5 +1,7 @@ | All NuGet feeds reachable | 1.0 | +| Failed project restore with missing package error | 0.0 | | Failed project restore with package source error | 0.0 | +| Failed solution restore with missing package error | 0.0 | | Failed solution restore with package source error | 0.0 | | Inherited NuGet feed count | 1.0 | | NuGet feed responsiveness checked | 1.0 | diff --git a/csharp/ql/integration-tests/all-platforms/standalone_winforms/CompilationInfo.expected b/csharp/ql/integration-tests/all-platforms/standalone_winforms/CompilationInfo.expected index 6b06566033c..63ddd4903f3 100644 --- a/csharp/ql/integration-tests/all-platforms/standalone_winforms/CompilationInfo.expected +++ b/csharp/ql/integration-tests/all-platforms/standalone_winforms/CompilationInfo.expected @@ -1,5 +1,7 @@ | All NuGet feeds reachable | 1.0 | +| Failed project restore with missing package error | 0.0 | | Failed project restore with package source error | 0.0 | +| Failed solution restore with missing package error | 0.0 | | Failed solution restore with package source error | 0.0 | | Inherited NuGet feed count | 1.0 | | NuGet feed responsiveness checked | 1.0 | diff --git a/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_error/CompilationInfo.expected b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_error/CompilationInfo.expected index 4cd316ce1e8..20fd0cdfba8 100644 --- a/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_error/CompilationInfo.expected +++ b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_error/CompilationInfo.expected @@ -1,5 +1,7 @@ | All NuGet feeds reachable | 0.0 | +| Failed project restore with missing package error | 1.0 | | Failed project restore with package source error | 0.0 | +| Failed solution restore with missing package error | 0.0 | | Failed solution restore with package source error | 0.0 | | Fallback nuget restore | 1.0 | | NuGet feed responsiveness checked | 1.0 | From ed857ad6e029a4d0ad2b329de9a210c9c8159b6d Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 24 Apr 2026 15:22:17 +0200 Subject: [PATCH 29/38] C#: Make the restore sources project/solution specific. --- .../NugetPackageRestorer.cs | 94 ++++++++++++------- 1 file changed, 61 insertions(+), 33 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index ab4fd27a7e7..9f0ac2f17de 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -119,7 +119,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching compilationInfoContainer.CompilationInfos.Add(("NuGet feed responsiveness checked", CheckNugetFeedResponsiveness ? "1" : "0")); HashSet explicitFeeds = []; - string? explicitNugetSources = null; + HashSet reachableFeeds = []; try { @@ -137,9 +137,11 @@ namespace Semmle.Extraction.CSharp.DependencyFetching compilationInfoContainer.CompilationInfos.Add(("Inherited NuGet feed count", inheritedFeeds.Count.ToString())); } - var timeout = CheckSpecifiedFeeds(explicitFeeds, out var reachableFeeds); - var allReachable = explicitFeeds.Count == reachableFeeds.Count; - EmitUnreachableFeedsDiagnostics(allReachable); + var timeout = CheckSpecifiedFeeds(explicitFeeds, out var reachableExplicitFeeds); + reachableFeeds.UnionWith(reachableExplicitFeeds); + + var allExplicitReachable = explicitFeeds.Count == reachableExplicitFeeds.Count; + EmitUnreachableFeedsDiagnostics(allExplicitReachable); if (timeout) { @@ -154,16 +156,6 @@ namespace Semmle.Extraction.CSharp.DependencyFetching // Inherited feeds should only be used, if they are indeed reachable (as they may be environment specific). CheckSpecifiedFeeds(inheritedFeeds, out var reachableInheritedFeeds); reachableFeeds.UnionWith(reachableInheritedFeeds); - - // 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). - 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. - explicitNugetSources = MakeRestoreSourcesArgument(allFeeds); } using (var nuget = new NugetExeWrapper(fileProvider, legacyPackageDirectory, logger, IsDefaultFeedReachable)) @@ -206,9 +198,9 @@ namespace Semmle.Extraction.CSharp.DependencyFetching } // Restore project dependencies with `dotnet restore`. - var restoredProjects = RestoreSolutions(explicitNugetSources, out var container); + var restoredProjects = RestoreSolutions(reachableFeeds, out var container); var projects = fileProvider.Projects.Except(restoredProjects); - RestoreProjects(projects, explicitNugetSources, out var containers); + RestoreProjects(projects, reachableFeeds, out var containers); var dependencies = containers.Flatten(container); @@ -313,7 +305,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching /// 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? nugetSources, out DependencyContainer dependencies) + private IEnumerable RestoreSolutions(HashSet reachableFeeds, out DependencyContainer dependencies) { var successCount = 0; var nugetSourceFailures = 0; @@ -326,6 +318,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching var projects = fileProvider.Solutions.SelectMany(solution => { logger.LogInfo($"Restoring solution {solution}..."); + var nugetSources = MakeRestoreSourcesArgument(solution, reachableFeeds); var res = dotnet.Restore(new(solution, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true, NugetSources: nugetSources, TargetWindows: isWindows)); if (res.Success) { @@ -350,7 +343,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching return projects; } - private string? MakeRestoreSourcesArgument(IEnumerable feeds) + private string FeedsToRestoreArgument(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()) @@ -369,14 +362,46 @@ namespace Semmle.Extraction.CSharp.DependencyFetching return feedArgs.ToString(); } + /// + /// Constructs the list of NuGet sources to use for this restore. + // (1) Use the feeds we get from `dotnet nuget list source` + // (2) Use private registries, if they are configured + /// + /// Path to project/solution + /// The set of reachable NuGet feeds. + /// A string representing the NuGet sources argument for the restore command. + private string? MakeRestoreSourcesArgument(string path, HashSet reachableFeeds) + { + // Do not construct an set of explicit NuGet sources to use for restore. + if (!CheckNugetFeedResponsiveness && !HasPrivateRegistryFeeds) + { + return null; + } + + // Find the path specific feeds. + var folder = GetDirectoryName(path); + var feedsToConsider = folder is not null ? GetFeeds(() => dotnet.GetNugetFeedsFromFolder(folder)).ToHashSet() : []; + + if (HasPrivateRegistryFeeds) + { + feedsToConsider.UnionWith(PrivateRegistryFeeds); + } + + var feedsToUse = CheckNugetFeedResponsiveness + ? feedsToConsider.Where(reachableFeeds.Contains) + : feedsToConsider; + + return FeedsToRestoreArgument(feedsToUse); + } + /// /// 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. - /// The explicit restore sources argument. - private void RestoreProjects(IEnumerable projects, string? explicitRestoreSources, out ConcurrentBag dependencies) + /// The set of reachable NuGet feeds. + private void RestoreProjects(IEnumerable projects, HashSet reachableFeeds, out ConcurrentBag dependencies) { var successCount = 0; var nugetSourceFailures = 0; @@ -393,7 +418,8 @@ namespace Semmle.Extraction.CSharp.DependencyFetching foreach (var project in projectGroup) { logger.LogInfo($"Restoring project {project}..."); - var res = dotnet.Restore(new(project, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true, NugetSources: explicitRestoreSources, TargetWindows: isWindows)); + var nugetSources = MakeRestoreSourcesArgument(project, reachableFeeds); + var res = dotnet.Restore(new(project, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true, NugetSources: nugetSources, TargetWindows: isWindows)); assets.AddDependenciesRange(res.AssetsFilePaths); lock (sync) { @@ -898,6 +924,19 @@ namespace Semmle.Extraction.CSharp.DependencyFetching } } + private string? GetDirectoryName(string path) + { + try + { + return new FileInfo(path).Directory?.FullName; + } + catch (Exception exc) + { + logger.LogWarning($"Failed to get directory of '{path}': {exc}"); + } + return null; + } + private (HashSet explicitFeeds, HashSet allFeeds) GetAllFeeds() { var nugetConfigs = fileProvider.NugetConfigs; @@ -968,18 +1007,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching { // 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. var nugetConfigFeeds = nugetConfigs - .Select(config => - { - try - { - return new FileInfo(config).Directory?.FullName; - } - catch (Exception exc) - { - logger.LogWarning($"Failed to get directory of '{config}': {exc}"); - } - return null; - }) + .Select(GetDirectoryName) .Where(folder => folder != null) .SelectMany(folder => GetFeeds(() => dotnet.GetNugetFeedsFromFolder(folder!))) .ToHashSet(); From ae81f3a00f7459d59e21fe8d4b15d9d08c70c35a Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 24 Apr 2026 15:24:34 +0200 Subject: [PATCH 30/38] C#: Inherited feeds may not get properly computed if a nuget.config file contains a clear. This has been fixed. --- .../NugetPackageRestorer.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 9f0ac2f17de..b80126adf04 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -1003,9 +1003,13 @@ namespace Semmle.Extraction.CSharp.DependencyFetching // Add all explicitFeeds to the set of all feeds. allFeeds.UnionWith(explicitFeeds); + // Obtain the list of feeds from the root source directory. + // If a NuGet file is present it will be respected, otherwise we will just get the machine/environment specific feeds. + var nugetFeedsFromRoot = GetFeeds(() => dotnet.GetNugetFeedsFromFolder(fileProvider.SourceDir.FullName)); + allFeeds.UnionWith(nugetFeedsFromRoot); + 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. var nugetConfigFeeds = nugetConfigs .Select(GetDirectoryName) .Where(folder => folder != null) @@ -1014,12 +1018,6 @@ namespace Semmle.Extraction.CSharp.DependencyFetching allFeeds.UnionWith(nugetConfigFeeds); } - else - { - // If we haven't found any `nuget.config` files, then obtain a list of feeds from the root source directory. - 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 615ae41e67f561010ff8f2d4df3d44b554fa6483 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Mon, 27 Apr 2026 16:03:13 +0200 Subject: [PATCH 31/38] C#: Address review comments. --- .../NugetPackageRestorer.cs | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index b80126adf04..6cc74349cb9 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; using System.Linq; using System.Net; @@ -30,9 +31,9 @@ namespace Semmle.Extraction.CSharp.DependencyFetching private readonly DependencyDirectory emptyPackageDirectory; private readonly ILogger logger; private readonly ICompilationInfoContainer compilationInfoContainer; - private bool CheckNugetFeedResponsiveness { get; } = EnvironmentVariables.GetBooleanOptOut(EnvironmentVariableNames.CheckNugetFeedResponsiveness); - private HashSet PrivateRegistryFeeds => dependabotProxy?.RegistryURLs ?? []; - private bool HasPrivateRegistryFeeds => PrivateRegistryFeeds.Any(); + private readonly bool checkNugetFeedResponsiveness = EnvironmentVariables.GetBooleanOptOut(EnvironmentVariableNames.CheckNugetFeedResponsiveness); + private readonly ImmutableHashSet privateRegistryFeeds; + private readonly bool hasPrivateRegistryFeeds; public DependencyDirectory PackageDirectory { get; } @@ -49,6 +50,8 @@ namespace Semmle.Extraction.CSharp.DependencyFetching this.fileContent = fileContent; this.dotnet = dotnet; this.dependabotProxy = dependabotProxy; + this.privateRegistryFeeds = dependabotProxy?.RegistryURLs.ToImmutableHashSet() ?? []; + this.hasPrivateRegistryFeeds = privateRegistryFeeds.Count > 0; this.diagnosticsWriter = diagnosticsWriter; this.logger = logger; this.compilationInfoContainer = compilationInfoContainer; @@ -115,8 +118,8 @@ namespace Semmle.Extraction.CSharp.DependencyFetching 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 = []; HashSet reachableFeeds = []; @@ -128,7 +131,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching // (including inherited ones) from other locations on the host outside of the working directory. (explicitFeeds, var allFeeds) = GetAllFeeds(); - if (CheckNugetFeedResponsiveness) + if (checkNugetFeedResponsiveness) { var inheritedFeeds = allFeeds.Except(explicitFeeds).ToHashSet(); @@ -212,7 +215,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching var usedPackageNames = GetAllUsedPackageDirNames(dependencies); - var missingPackageLocation = CheckNugetFeedResponsiveness + var missingPackageLocation = checkNugetFeedResponsiveness ? DownloadMissingPackagesFromSpecificFeeds(usedPackageNames, explicitFeeds) : DownloadMissingPackages(usedPackageNames); @@ -261,7 +264,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching private bool IsDefaultFeedReachable() { - if (CheckNugetFeedResponsiveness) + if (checkNugetFeedResponsiveness) { var (initialTimeout, tryCount) = GetFeedRequestSettings(isFallback: false); return IsFeedReachable(PublicNugetOrgFeed, initialTimeout, tryCount, out var _); @@ -373,7 +376,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching private string? MakeRestoreSourcesArgument(string path, HashSet reachableFeeds) { // Do not construct an set of explicit NuGet sources to use for restore. - if (!CheckNugetFeedResponsiveness && !HasPrivateRegistryFeeds) + if (!checkNugetFeedResponsiveness && !hasPrivateRegistryFeeds) { return null; } @@ -382,12 +385,12 @@ namespace Semmle.Extraction.CSharp.DependencyFetching var folder = GetDirectoryName(path); var feedsToConsider = folder is not null ? GetFeeds(() => dotnet.GetNugetFeedsFromFolder(folder)).ToHashSet() : []; - if (HasPrivateRegistryFeeds) + if (hasPrivateRegistryFeeds) { - feedsToConsider.UnionWith(PrivateRegistryFeeds); + feedsToConsider.UnionWith(privateRegistryFeeds); } - var feedsToUse = CheckNugetFeedResponsiveness + var feedsToUse = checkNugetFeedResponsiveness ? feedsToConsider.Where(reachableFeeds.Contains) : feedsToConsider; @@ -992,10 +995,10 @@ namespace Semmle.Extraction.CSharp.DependencyFetching // 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) + if (hasPrivateRegistryFeeds) { - logger.LogInfo($"Found {PrivateRegistryFeeds.Count} private registry feeds configured for C#: {string.Join(", ", PrivateRegistryFeeds.OrderBy(f => f))}"); - explicitFeeds.UnionWith(PrivateRegistryFeeds); + logger.LogInfo($"Found {privateRegistryFeeds.Count} private registry feeds configured for C#: {string.Join(", ", privateRegistryFeeds.OrderBy(f => f))}"); + explicitFeeds.UnionWith(privateRegistryFeeds); } HashSet allFeeds = []; From 67aa342fe5482a2480abbb96720ee8adc56ffabe Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Tue, 28 Apr 2026 12:46:41 +0200 Subject: [PATCH 32/38] C#: Update test expected output for integration tests. --- .../CompilationInfo.expected | 1 + .../CompilationInfo.expected | 1 + 2 files changed, 2 insertions(+) diff --git a/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_error/CompilationInfo.expected b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_error/CompilationInfo.expected index 20fd0cdfba8..4acd4f54e8a 100644 --- a/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_error/CompilationInfo.expected +++ b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_error/CompilationInfo.expected @@ -4,6 +4,7 @@ | Failed solution restore with missing package error | 0.0 | | Failed solution restore with package source error | 0.0 | | Fallback nuget restore | 1.0 | +| Inherited NuGet feed count | 1.0 | | NuGet feed responsiveness checked | 1.0 | | Project files on filesystem | 1.0 | | Reachable fallback NuGet feed count | 1.0 | diff --git a/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_fallback/CompilationInfo.expected b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_fallback/CompilationInfo.expected index c53a17f0300..9cc03f2f537 100644 --- a/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_fallback/CompilationInfo.expected +++ b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_config_fallback/CompilationInfo.expected @@ -1,5 +1,6 @@ | All NuGet feeds reachable | 0.0 | | Fallback nuget restore | 1.0 | +| Inherited NuGet feed count | 1.0 | | NuGet feed responsiveness checked | 1.0 | | Project files on filesystem | 1.0 | | Reachable fallback NuGet feed count | 2.0 | From dfd85c321ca8c99f1986f4ab0179304a85232a12 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 28 Apr 2026 22:02:32 +0100 Subject: [PATCH 33/38] C++: Compute 'IgnorableOperationToOperationSourceCandidateConfig' after an initial round of the query to reduce the number of sinks. --- .../UncheckedLeapYearAfterYearModification.ql | 144 +++++++++++------- 1 file changed, 87 insertions(+), 57 deletions(-) diff --git a/cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql b/cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql index 0a52a2b0ff4..1320069bb1c 100644 --- a/cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql +++ b/cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql @@ -227,6 +227,30 @@ class IgnorableUnaryBitwiseOperation extends IgnorableOperation instanceof Unary class IgnorableAssignmentBitwiseOperation extends IgnorableOperation instanceof AssignBitwiseOperation { } +class YearFieldAssignmentNode extends DataFlow::Node { + YearFieldAccess access; + + YearFieldAssignmentNode() { + exists(Function f | + f = this.getEnclosingCallable().getUnderlyingCallable() and not f instanceof IgnorableFunction + | + this.asDefinition().(Assignment).getLValue() = access + or + this.asDefinition().(CrementOperation).getOperand() = access + or + exists(Call c | c.getAnArgument() = access and this.asDefiningArgument() = access) + or + exists(Call c, AddressOfExpr aoe | + c.getAnArgument() = aoe and + aoe.getOperand() = access and + this.asDefiningArgument() = aoe + ) + ) + } + + YearFieldAccess getYearFieldAccess() { result = access } +} + /** * An arithmetic operation where one of the operands is a pointer or char type, ignore it */ @@ -287,24 +311,7 @@ predicate isOperationSourceCandidate(Expr e) { } /** - * A data flow that tracks an ignorable operation (such as a bitwise operation) to an operation source, so we may disqualify it. - */ -module IgnorableOperationToOperationSourceCandidateConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node n) { n.asExpr() instanceof IgnorableOperation } - - predicate isSink(DataFlow::Node n) { isOperationSourceCandidate(n.asExpr()) } - - // looking for sources and sinks in the same function - DataFlow::FlowFeature getAFeature() { - result instanceof DataFlow::FeatureEqualSourceSinkCallContext - } -} - -module IgnorableOperationToOperationSourceCandidateFlow = - TaintTracking::Global; - -/** - * The set of all expressions which is a candidate expression and also does not flow from to to some ignorable expression (eg. bitwise op) + * The set of all expressions which is a candidate expression. * ``` * a = something <<< 2; * myDate.year = a + 1; // invalid @@ -314,49 +321,16 @@ module IgnorableOperationToOperationSourceCandidateFlow = * ``` */ class OperationSource extends Expr { - OperationSource() { - isOperationSourceCandidate(this) and - // If the candidate came from an ignorable operation, ignore the candidate - // NOTE: we cannot easily flow the candidate to an ignorable operation as that can - // be tricky in practice, e.g., a mod operation on a year would be part of a leap year check - // but a mod operation ending in a year is more indicative of something to ignore (a conversion) - not exists(IgnorableOperationToOperationSourceCandidateFlow::PathNode sink | - sink.getNode().asExpr() = this and - sink.isSink() - ) - } -} - -class YearFieldAssignmentNode extends DataFlow::Node { - YearFieldAccess access; - - YearFieldAssignmentNode() { - exists(Function f | - f = this.getEnclosingCallable().getUnderlyingCallable() and not f instanceof IgnorableFunction - ) and - ( - this.asDefinition().(Assignment).getLValue() = access - or - this.asDefinition().(CrementOperation).getOperand() = access - or - exists(Call c | c.getAnArgument() = access and this.asDefiningArgument() = access) - or - exists(Call c, AddressOfExpr aoe | - c.getAnArgument() = aoe and - aoe.getOperand() = access and - this.asDefiningArgument() = aoe - ) - ) - } - - YearFieldAccess getYearFieldAccess() { result = access } + OperationSource() { isOperationSourceCandidate(this) } } /** - * A DataFlow configuration for identifying flows from an identified source - * to the Year field of a date object. + * An initial DataFlow configuration for identifying flows from an identified source + * to the Year field of a date object. This is used to restrict the sinks of + * `IgnorableOperationToOperationSourceCandidateConfig` and the sinks of the + * final `OperationToYearAssignmentConfig`. */ -module OperationToYearAssignmentConfig implements DataFlow::ConfigSig { +module OperationToYearAssignmentConfig0 implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node n) { n.asExpr() instanceof OperationSource } predicate isSink(DataFlow::Node n) { @@ -411,6 +385,62 @@ module OperationToYearAssignmentConfig implements DataFlow::ConfigSig { predicate isBarrierOut(DataFlow::Node n) { isSink(n) } } +module OperationToYearAssignmentFlow0 = TaintTracking::Global; + +predicate yearAssignmentFlowsFromSource(DataFlow::Node source, DataFlow::Node sink) { + OperationToYearAssignmentFlow0::flow(source, sink) +} + +/** + * A data flow that tracks an ignorable operation (such as a bitwise operation) to an operation source, so we may disqualify it. + * Sinks are restricted to operation source candidates that have a flow to a year assignment in `OperationToYearAssignmentFlow0`. + */ +module IgnorableOperationToOperationSourceCandidateConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node n) { n.asExpr() instanceof IgnorableOperation } + + predicate isSink(DataFlow::Node n) { + isOperationSourceCandidate(n.asExpr()) and + yearAssignmentFlowsFromSource(n, _) + } + + // looking for sources and sinks in the same function + DataFlow::FlowFeature getAFeature() { + result instanceof DataFlow::FeatureEqualSourceSinkCallContext + } +} + +module IgnorableOperationToOperationSourceCandidateFlow = + TaintTracking::Global; + +/** + * The final DataFlow configuration that refines `OperationToYearAssignmentConfig0` by + * additionally filtering out operation sources that flow from an ignorable operation + * (via `IgnorableOperationToOperationSourceCandidateFlow`). + */ +module OperationToYearAssignmentConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node n) { yearAssignmentFlowsFromSource(n, _) } + + predicate isSink(DataFlow::Node n) { + exists(DataFlow::Node operation | + yearAssignmentFlowsFromSource(operation, n) and + // If the candidate came from an ignorable operation, ignore the candidate + // NOTE: we cannot easily flow the candidate to an ignorable operation as that can + // be tricky in practice, e.g., a mod operation on a year would be part of a leap year check + // but a mod operation ending in a year is more indicative of something to ignore (a conversion) + not exists(IgnorableOperationToOperationSourceCandidateFlow::PathNode sink | + sink.getNode() = operation and + sink.isSink() + ) + ) + } + + predicate isBarrier(DataFlow::Node n) { OperationToYearAssignmentConfig0::isBarrier(n) } + + predicate isBarrierIn(DataFlow::Node n) { isSource(n) } + + predicate isBarrierOut(DataFlow::Node n) { isSink(n) } +} + module OperationToYearAssignmentFlow = TaintTracking::Global; predicate isLeapYearCheckSink(DataFlow::Node sink) { From 96d6ee61ffde3ce28df1feec97b1e0200ed5bc13 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 29 Apr 2026 10:55:02 +0100 Subject: [PATCH 34/38] Update cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql Co-authored-by: Jeroen Ketema <93738568+jketema@users.noreply.github.com> --- .../Leap Year/UncheckedLeapYearAfterYearModification.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql b/cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql index 1320069bb1c..4bc58eb0854 100644 --- a/cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql +++ b/cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql @@ -311,7 +311,7 @@ predicate isOperationSourceCandidate(Expr e) { } /** - * The set of all expressions which is a candidate expression. + * The set of all expressions that are candidate expression. * ``` * a = something <<< 2; * myDate.year = a + 1; // invalid From 75162bb9eb4efbd6617cca09483ea777db8338fc Mon Sep 17 00:00:00 2001 From: MarkLee131 Date: Wed, 29 Apr 2026 20:53:58 +0800 Subject: [PATCH 35/38] Update java/ql/test/query-tests/security/CWE-532/Test.java Co-authored-by: Owen Mansel-Chan <62447351+owen-mc@users.noreply.github.com> --- java/ql/test/query-tests/security/CWE-532/Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/test/query-tests/security/CWE-532/Test.java b/java/ql/test/query-tests/security/CWE-532/Test.java index a25becb89bb..cf1ca0aee68 100644 --- a/java/ql/test/query-tests/security/CWE-532/Test.java +++ b/java/ql/test/query-tests/security/CWE-532/Test.java @@ -28,7 +28,7 @@ class Test { logger.info("hash: " + hashPassword(password)); // Safe - hashed logger.info("hash: " + sha256Digest(authToken)); // Safe - digested logger.info("enc: " + encryptValue(password)); // Safe - encrypted - logger.info("pw: " + password); // $ Alert - not hashed + logger.info("pw: " + password); // $ Alert // not hashed } static String hashPassword(String input) { return input; } From 51e2a5418b7558c6ef86019afa68c37242b85cd6 Mon Sep 17 00:00:00 2001 From: MarkLee131 Date: Wed, 29 Apr 2026 20:56:36 +0800 Subject: [PATCH 36/38] Java: move EncryptedSensitiveMethodCall into Sanitizers.qll Address review feedback by moving the shared method-name-based encryption/hash/digest check into Sanitizers.qll, and reference it from both CleartextStorageQuery.qll and SensitiveLoggingQuery.qll instead of duplicating the definition. --- .../code/java/security/CleartextStorageQuery.qll | 12 +----------- java/ql/lib/semmle/code/java/security/Sanitizers.qll | 11 +++++++++++ .../code/java/security/SensitiveLoggingQuery.qll | 7 +------ 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/java/ql/lib/semmle/code/java/security/CleartextStorageQuery.qll b/java/ql/lib/semmle/code/java/security/CleartextStorageQuery.qll index 21d82bef657..83f51f7eedf 100644 --- a/java/ql/lib/semmle/code/java/security/CleartextStorageQuery.qll +++ b/java/ql/lib/semmle/code/java/security/CleartextStorageQuery.qll @@ -2,6 +2,7 @@ import java private import semmle.code.java.dataflow.TaintTracking +private import semmle.code.java.security.Sanitizers private import semmle.code.java.security.SensitiveActions /** A sink representing persistent storage that saves data in clear text. */ @@ -76,17 +77,6 @@ private class DefaultCleartextStorageSanitizer extends CleartextStorageSanitizer } } -/** - * Method call for encrypting sensitive information. As there are various implementations of - * encryption (reversible and non-reversible) from both JDK and third parties, this class simply - * checks method name to take a best guess to reduce false positives. - */ -private class EncryptedSensitiveMethodCall extends MethodCall { - EncryptedSensitiveMethodCall() { - this.getMethod().getName().toLowerCase().matches(["%encrypt%", "%hash%", "%digest%"]) - } -} - /** Flow configuration for encryption methods flowing to inputs of persistent storage. */ private module EncryptedValueFlowConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node src) { src.asExpr() instanceof EncryptedSensitiveMethodCall } diff --git a/java/ql/lib/semmle/code/java/security/Sanitizers.qll b/java/ql/lib/semmle/code/java/security/Sanitizers.qll index e00071da2d8..0c5f9b98070 100644 --- a/java/ql/lib/semmle/code/java/security/Sanitizers.qll +++ b/java/ql/lib/semmle/code/java/security/Sanitizers.qll @@ -63,3 +63,14 @@ class RegexpCheckBarrier extends DataFlow::Node { exists(RegexMatch rm | rm instanceof Annotation | this.asExpr() = rm.getString()) } } + +/** + * A method call for encrypting, hashing, or digesting sensitive information. As there are various + * implementations of encryption (reversible and non-reversible) from both JDK and third parties, + * this class simply checks the method name to take a best guess to reduce false positives. + */ +class EncryptedSensitiveMethodCall extends MethodCall { + EncryptedSensitiveMethodCall() { + this.getMethod().getName().toLowerCase().matches(["%encrypt%", "%hash%", "%digest%"]) + } +} diff --git a/java/ql/lib/semmle/code/java/security/SensitiveLoggingQuery.qll b/java/ql/lib/semmle/code/java/security/SensitiveLoggingQuery.qll index 5f11ae0d214..f35cae0e67f 100644 --- a/java/ql/lib/semmle/code/java/security/SensitiveLoggingQuery.qll +++ b/java/ql/lib/semmle/code/java/security/SensitiveLoggingQuery.qll @@ -125,12 +125,7 @@ private class DefaultSensitiveLoggerBarrier extends SensitiveLoggerBarrier { * This is consistent with the treatment of encryption in `CleartextStorageQuery.qll` (CWE-312). */ private class EncryptionBarrier extends SensitiveLoggerBarrier { - EncryptionBarrier() { - exists(MethodCall mc | - this.asExpr() = mc and - mc.getMethod().getName().toLowerCase().matches(["%encrypt%", "%hash%", "%digest%"]) - ) - } + EncryptionBarrier() { this.asExpr() instanceof EncryptedSensitiveMethodCall } } /** A data-flow configuration for identifying potentially-sensitive data flowing to a log output. */ From e29770c2b5f57b4dded344a394f237b92920e8f4 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 29 Apr 2026 15:27:47 +0200 Subject: [PATCH 37/38] C#: Fix missing slash in comments. --- .../NugetPackageRestorer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 6cc74349cb9..e042285af11 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -367,8 +367,8 @@ namespace Semmle.Extraction.CSharp.DependencyFetching /// /// Constructs the list of NuGet sources to use for this restore. - // (1) Use the feeds we get from `dotnet nuget list source` - // (2) Use private registries, if they are configured + /// (1) Use the feeds we get from `dotnet nuget list source` + /// (2) Use private registries, if they are configured /// /// Path to project/solution /// The set of reachable NuGet feeds. From 03d70b9f94e0537ea1fa1dfddeb2d4192df7883a Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 29 Apr 2026 15:47:32 +0200 Subject: [PATCH 38/38] C#: Add another nuget.config integration test. --- .../Assemblies.expected | 1 + .../Assemblies.ql | 5 +++++ .../CompilationInfo.expected | 22 +++++++++++++++++++ .../CompilationInfo.ql | 15 +++++++++++++ .../clear/nuget.config | 6 +++++ .../global.json | 5 +++++ .../proj/Program.cs | 1 + .../proj/proj.csproj | 16 ++++++++++++++ .../test.py | 12 ++++++++++ 9 files changed, 83 insertions(+) create mode 100644 csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/Assemblies.expected create mode 100644 csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/Assemblies.ql create mode 100644 csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/CompilationInfo.expected create mode 100644 csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/CompilationInfo.ql create mode 100644 csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/clear/nuget.config create mode 100644 csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/global.json create mode 100644 csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/proj/Program.cs create mode 100644 csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/proj/proj.csproj create mode 100644 csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/test.py diff --git a/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/Assemblies.expected b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/Assemblies.expected new file mode 100644 index 00000000000..b676e41c184 --- /dev/null +++ b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/Assemblies.expected @@ -0,0 +1 @@ +| test-db/working/packages/newtonsoft.json/13.0.4/lib/net6.0/Newtonsoft.Json.dll:0:0:0:0 | Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed | diff --git a/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/Assemblies.ql b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/Assemblies.ql new file mode 100644 index 00000000000..0eb33b7ae37 --- /dev/null +++ b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/Assemblies.ql @@ -0,0 +1,5 @@ +import csharp + +from Assembly a +where exists(a.getFile().getAbsolutePath().indexOf("newtonsoft.json")) +select a diff --git a/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/CompilationInfo.expected b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/CompilationInfo.expected new file mode 100644 index 00000000000..ff0b29da33f --- /dev/null +++ b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/CompilationInfo.expected @@ -0,0 +1,22 @@ +| All NuGet feeds reachable | 1.0 | +| Failed project restore with missing package error | 0.0 | +| Failed project restore with package source error | 0.0 | +| Failed solution restore with missing package error | 0.0 | +| Failed solution restore with package source error | 0.0 | +| Inherited NuGet feed count | 1.0 | +| NuGet feed responsiveness checked | 1.0 | +| Project files on filesystem | 1.0 | +| Reachable fallback NuGet feed count | 1.0 | +| Resolved assembly conflicts | 0.0 | +| Resource extraction enabled | 0.0 | +| Restored .NET framework variants | 1.0 | +| Restored projects through solution files | 0.0 | +| Solution files on filesystem | 0.0 | +| Source files generated | 0.0 | +| Source files on filesystem | 1.0 | +| Successfully restored project files | 1.0 | +| Successfully restored solution files | 0.0 | +| Unresolved references | 0.0 | +| UseWPF set | 0.0 | +| UseWindowsForms set | 0.0 | +| WebView extraction enabled | 1.0 | diff --git a/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/CompilationInfo.ql b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/CompilationInfo.ql new file mode 100644 index 00000000000..073ffe3b224 --- /dev/null +++ b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/CompilationInfo.ql @@ -0,0 +1,15 @@ +import csharp +import semmle.code.csharp.commons.Diagnostics + +query predicate compilationInfo(string key, float value) { + key != "Resolved references" and + not key.matches("Compiler diagnostic count for%") and + exists(Compilation c, string infoKey, string infoValue | infoValue = c.getInfo(infoKey) | + key = infoKey and + value = infoValue.toFloat() + or + not exists(infoValue.toFloat()) and + key = infoKey + ": " + infoValue and + value = 1 + ) +} diff --git a/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/clear/nuget.config b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/clear/nuget.config new file mode 100644 index 00000000000..a3f3aea61c8 --- /dev/null +++ b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/clear/nuget.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/global.json b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/global.json new file mode 100644 index 00000000000..ed604974070 --- /dev/null +++ b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "10.0.201" + } +} diff --git a/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/proj/Program.cs b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/proj/Program.cs new file mode 100644 index 00000000000..c340f4c32fd --- /dev/null +++ b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/proj/Program.cs @@ -0,0 +1 @@ +class Program { } diff --git a/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/proj/proj.csproj b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/proj/proj.csproj new file mode 100644 index 00000000000..6010c6c7f37 --- /dev/null +++ b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/proj/proj.csproj @@ -0,0 +1,16 @@ + + + + Exe + net10.0 + + + + + + + + + + + diff --git a/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/test.py b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/test.py new file mode 100644 index 00000000000..cbbf2eb1d64 --- /dev/null +++ b/csharp/ql/integration-tests/posix/standalone_dependencies_nuget_clear/test.py @@ -0,0 +1,12 @@ +import os +import runs_on + + +@runs_on.posix +def test(codeql, csharp): + # Making sure the reachability test of `nuget.org` succeeds: + os.environ["CODEQL_EXTRACTOR_CSHARP_BUILDLESS_NUGET_FEEDS_CHECK_FALLBACK_TIMEOUT"] = "1000" + os.environ["CODEQL_EXTRACTOR_CSHARP_BUILDLESS_NUGET_FEEDS_CHECK_FALLBACK_LIMIT"] = "5" + + # This test checks that the nuget.config file in the clear folder is not applied to the restore of the project. + codeql.database.create(build_mode="none")