From 76fe3fa5027d2acaa55673d05494d026d301230c Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 29 Jan 2026 21:40:19 +0000 Subject: [PATCH 1/7] C#: Make sure `allFeeds` contains at least `explicitFeeds` --- .../NugetPackageRestorer.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 29cec8a0e339..923e380b7779 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -856,6 +856,17 @@ private IEnumerable GetFeeds(Func> getNugetFeeds) allFeeds = GetFeeds(() => dotnet.GetNugetFeedsFromFolder(this.fileProvider.SourceDir.FullName)).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. + if (explicitFeeds.Count > 0) + { + allFeeds.UnionWith(explicitFeeds); + } + logger.LogInfo($"Found {allFeeds.Count} NuGet feeds (with inherited ones) in nuget.config files: {string.Join(", ", allFeeds.OrderBy(f => f))}"); return (explicitFeeds, allFeeds); From 5ba3b679dd48e4b3a8eac371a170702bc739ff3c Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 30 Jan 2026 12:18:56 +0000 Subject: [PATCH 2/7] Move into if statement --- .../NugetPackageRestorer.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 923e380b7779..4fce6f3eb65d 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -849,6 +849,17 @@ private IEnumerable GetFeeds(Func> getNugetFeeds) .Where(folder => folder != null) .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. + if (explicitFeeds.Count > 0) + { + allFeeds.UnionWith(explicitFeeds); + } } else { @@ -856,17 +867,6 @@ private IEnumerable GetFeeds(Func> getNugetFeeds) allFeeds = GetFeeds(() => dotnet.GetNugetFeedsFromFolder(this.fileProvider.SourceDir.FullName)).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. - if (explicitFeeds.Count > 0) - { - allFeeds.UnionWith(explicitFeeds); - } - logger.LogInfo($"Found {allFeeds.Count} NuGet feeds (with inherited ones) in nuget.config files: {string.Join(", ", allFeeds.OrderBy(f => f))}"); return (explicitFeeds, allFeeds); From 1b5ed129ac28255201b2bc0e32965e2694543206 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 30 Jan 2026 13:09:01 +0000 Subject: [PATCH 3/7] Log and emit diagnostic if incorrectly named files are found --- .../BuildScripts.cs | 4 ++ .../BuildScripts.cs | 4 ++ .../NugetPackageRestorer.cs | 37 +++++++++++++++++++ csharp/extractor/Semmle.Util/BuildActions.cs | 8 ++++ 4 files changed, 53 insertions(+) diff --git a/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs b/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs index 7eb0d539812c..a8ce96539169 100644 --- a/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs +++ b/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs @@ -158,6 +158,10 @@ IEnumerable IBuildActions.EnumerateDirectories(string dir) bool IBuildActions.IsMacOs() => IsMacOs; + public bool IsLinux { get; set; } + + bool IBuildActions.IsLinux() => IsLinux; + public bool IsRunningOnAppleSilicon { get; set; } bool IBuildActions.IsRunningOnAppleSilicon() => IsRunningOnAppleSilicon; diff --git a/csharp/autobuilder/Semmle.Autobuild.Cpp.Tests/BuildScripts.cs b/csharp/autobuilder/Semmle.Autobuild.Cpp.Tests/BuildScripts.cs index afa4ea4b41c3..fd5e4073d6d9 100644 --- a/csharp/autobuilder/Semmle.Autobuild.Cpp.Tests/BuildScripts.cs +++ b/csharp/autobuilder/Semmle.Autobuild.Cpp.Tests/BuildScripts.cs @@ -146,6 +146,10 @@ IEnumerable IBuildActions.EnumerateDirectories(string dir) bool IBuildActions.IsMacOs() => IsMacOs; + public bool IsLinux { get; set; } + + bool IBuildActions.IsLinux() => IsLinux; + public bool IsRunningOnAppleSilicon { get; set; } bool IBuildActions.IsRunningOnAppleSilicon() => IsRunningOnAppleSilicon; diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 4fce6f3eb65d..8c64798987f7 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -814,6 +814,43 @@ private IEnumerable GetFeeds(Func> getNugetFeeds) private (HashSet explicitFeeds, HashSet allFeeds) GetAllFeeds() { var nugetConfigs = fileProvider.NugetConfigs; + + // On systems with case-sensitive file systems (for simplicity, we assume that is Linux), the + // filenames of NuGet configuration files must be named correctly. For compatibility with projects + // that are typically built on Windows or macOS where this doesn't matter, we accept all variants + // of `nuget.config` ourselves. However, `dotnet` does not. If we detect that incorrectly-named + // files are present, we emit a diagnostic to warn the user. + if (SystemBuildActions.Instance.IsLinux()) + { + string[] acceptedNugetConfigNames = ["nuget.config", "NuGet.config", "NuGet.Config"]; + var invalidNugetConfigs = nugetConfigs + .Where(path => acceptedNugetConfigNames.Contains(Path.GetFileName(path))); + + if (invalidNugetConfigs.Count() > 0) + { + this.logger.LogWarning(string.Format( + "Found incorrectly named NuGet configuration files: {0}", + string.Join(", ", invalidNugetConfigs) + )); + this.diagnosticsWriter.AddEntry(new DiagnosticMessage( + Language.CSharp, + "buildless/case-sensitive-nuget-config", + "Found NuGet configuration files which are not correctly named", + visibility: new DiagnosticMessage.TspVisibility(statusPage: true, cliSummaryTable: true, telemetry: true), + markdownMessage: string.Format( + "On platforms with case-sensitive file systems, NuGet only accepts files with one of the following names: {0}.\n\n" + + "CodeQL found the following files while performing an analysis on a platform with a case-sensitive file system:\n\n" + + "{1}\n\n" + + "To avoid unexpected results, rename these files to match the casing of one of the accepted filenames.", + string.Join(", ", acceptedNugetConfigNames), + string.Join("\n", invalidNugetConfigs.Select(path => string.Format("- `{0}`", path))) + ), + severity: DiagnosticMessage.TspSeverity.Warning + )); + } + } + + // Find feeds that are explicitly configured in the NuGet configuration files that we found. var explicitFeeds = nugetConfigs .SelectMany(config => GetFeeds(() => dotnet.GetNugetFeeds(config))) .ToHashSet(); diff --git a/csharp/extractor/Semmle.Util/BuildActions.cs b/csharp/extractor/Semmle.Util/BuildActions.cs index 38210402945b..09696564efc5 100644 --- a/csharp/extractor/Semmle.Util/BuildActions.cs +++ b/csharp/extractor/Semmle.Util/BuildActions.cs @@ -119,6 +119,12 @@ public interface IBuildActions /// True if we are running on macOS. bool IsMacOs(); + /// + /// Gets a value indicating whether we are running on Linux. + /// + /// True if we are running on Linux. + bool IsLinux(); + /// /// Gets a value indicating whether we are running on Apple Silicon. /// @@ -246,6 +252,8 @@ int IBuildActions.RunProcess(string cmd, string args, string? workingDirectory, bool IBuildActions.IsMacOs() => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + bool IBuildActions.IsLinux() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + bool IBuildActions.IsRunningOnAppleSilicon() { var thisBuildActions = (IBuildActions)this; From 1aba0b20cd2acf197d83aafb7d67c6117a582912 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 30 Jan 2026 13:09:33 +0000 Subject: [PATCH 4/7] Add integration test --- .../linux/diag_nuget_config_casing/Program.cs | 6 ++++ .../diagnostics.expected | 28 +++++++++++++++++++ .../diag_nuget_config_casing/global.json | 5 ++++ .../sub-project/Nuget.Config | 5 ++++ .../diag_nuget_config_casing/test.csproj | 8 ++++++ .../linux/diag_nuget_config_casing/test.py | 5 ++++ 6 files changed, 57 insertions(+) create mode 100644 csharp/ql/integration-tests/linux/diag_nuget_config_casing/Program.cs create mode 100644 csharp/ql/integration-tests/linux/diag_nuget_config_casing/diagnostics.expected create mode 100644 csharp/ql/integration-tests/linux/diag_nuget_config_casing/global.json create mode 100644 csharp/ql/integration-tests/linux/diag_nuget_config_casing/sub-project/Nuget.Config create mode 100644 csharp/ql/integration-tests/linux/diag_nuget_config_casing/test.csproj create mode 100644 csharp/ql/integration-tests/linux/diag_nuget_config_casing/test.py diff --git a/csharp/ql/integration-tests/linux/diag_nuget_config_casing/Program.cs b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/Program.cs new file mode 100644 index 000000000000..39a9e95bb6e3 --- /dev/null +++ b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/Program.cs @@ -0,0 +1,6 @@ +class Program +{ + static void Main(string[] args) + { + } +} \ No newline at end of file diff --git a/csharp/ql/integration-tests/linux/diag_nuget_config_casing/diagnostics.expected b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/diagnostics.expected new file mode 100644 index 000000000000..f53cd8e15987 --- /dev/null +++ b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/diagnostics.expected @@ -0,0 +1,28 @@ +{ + "markdownMessage": "C# analysis with build-mode 'none' completed.", + "severity": "unknown", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/buildless/complete", + "name": "C# analysis with build-mode 'none' completed" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": false, + "telemetry": true + } +} +{ + "markdownMessage": "C# was extracted with build-mode set to 'none'. This means that all C# source in the working directory will be scanned, with build tools, such as NuGet and dotnet CLIs, only contributing information about external dependencies.", + "severity": "note", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/buildless/mode-active", + "name": "C# was extracted with build-mode set to 'none'" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} diff --git a/csharp/ql/integration-tests/linux/diag_nuget_config_casing/global.json b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/global.json new file mode 100644 index 000000000000..481e95ec7be1 --- /dev/null +++ b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "10.0.100" + } +} diff --git a/csharp/ql/integration-tests/linux/diag_nuget_config_casing/sub-project/Nuget.Config b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/sub-project/Nuget.Config new file mode 100644 index 000000000000..aa5beec8aa09 --- /dev/null +++ b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/sub-project/Nuget.Config @@ -0,0 +1,5 @@ + + + + + diff --git a/csharp/ql/integration-tests/linux/diag_nuget_config_casing/test.csproj b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/test.csproj new file mode 100644 index 000000000000..a15a29bf12c2 --- /dev/null +++ b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/test.csproj @@ -0,0 +1,8 @@ + + + + Exe + net10.0 + + + diff --git a/csharp/ql/integration-tests/linux/diag_nuget_config_casing/test.py b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/test.py new file mode 100644 index 000000000000..a5d5f3fe03a4 --- /dev/null +++ b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/test.py @@ -0,0 +1,5 @@ +import runs_on + +@runs_on.linux +def test(codeql, csharp): + codeql.database.create(build_mode="none") From 3e0719609fb9ec20f276666b0275bf6e29b5fb78 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 30 Jan 2026 13:30:47 +0000 Subject: [PATCH 5/7] Fix missing negation --- .../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 8c64798987f7..6093e43e5137 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -824,7 +824,7 @@ private IEnumerable GetFeeds(Func> getNugetFeeds) { string[] acceptedNugetConfigNames = ["nuget.config", "NuGet.config", "NuGet.Config"]; var invalidNugetConfigs = nugetConfigs - .Where(path => acceptedNugetConfigNames.Contains(Path.GetFileName(path))); + .Where(path => !acceptedNugetConfigNames.Contains(Path.GetFileName(path))); if (invalidNugetConfigs.Count() > 0) { From ad2aa6d4f8a41bd4be3765b04efc032dbb638e1c Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 30 Jan 2026 13:38:50 +0000 Subject: [PATCH 6/7] Accept expected diagnostic output --- .../diag_nuget_config_casing/diagnostics.expected | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/csharp/ql/integration-tests/linux/diag_nuget_config_casing/diagnostics.expected b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/diagnostics.expected index f53cd8e15987..063a2659f861 100644 --- a/csharp/ql/integration-tests/linux/diag_nuget_config_casing/diagnostics.expected +++ b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/diagnostics.expected @@ -26,3 +26,17 @@ "telemetry": true } } +{ + "markdownMessage": "On platforms with case-sensitive file systems, NuGet only accepts files with one of the following names: nuget.config, NuGet.config, NuGet.Config.\n\nCodeQL found the following files while performing an analysis on a platform with a case-sensitive file system:\n\n- `/sub-project/Nuget.Config`\n\nTo avoid unexpected results, rename these files to match the casing of one of the accepted filenames.", + "severity": "warning", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/buildless/case-sensitive-nuget-config", + "name": "Found NuGet configuration files which are not correctly named" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} From 454d13b48582bf2a89d9d26731443905eec654ff Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 30 Jan 2026 14:03:43 +0000 Subject: [PATCH 7/7] Remove element check --- .../NugetPackageRestorer.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 6093e43e5137..1d01412ee051 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -893,10 +893,7 @@ private IEnumerable GetFeeds(Func> getNugetFeeds) // 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. - if (explicitFeeds.Count > 0) - { - allFeeds.UnionWith(explicitFeeds); - } + allFeeds.UnionWith(explicitFeeds); } else {