From d62fae8060609ba8e8a3e46e4a66a6ddfcb6c8c4 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Mon, 5 Oct 2026 15:23:09 +0200 Subject: [PATCH 1/6] C#: Re-factor logic for setting .NET proxy environment variables into the RegistryProxy class and add a unit test. --- .../DotNetCliInvoker.cs | 9 +---- .../IRegistryProxy.cs | 7 ++++ .../RegistryProxy.cs | 22 +++++++++++- .../Semmle.Extraction.Tests/FeedManager.cs | 4 +++ .../Semmle.Extraction.Tests/RegistryProxy.cs | 34 +++++++++++++++++++ 5 files changed, 67 insertions(+), 9 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNetCliInvoker.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNetCliInvoker.cs index 384404fbc6ce..467a90ac7288 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNetCliInvoker.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DotNetCliInvoker.cs @@ -44,14 +44,7 @@ private ProcessStartInfo MakeDotnetStartInfo(List args, string? workingD } // Configure the proxy settings, if applicable. - if (this.proxy != null) - { - logger.LogDebug($"Configuring environment variables for the registry proxy at {this.proxy.Address}"); - - startInfo.EnvironmentVariables["HTTP_PROXY"] = this.proxy.Address; - startInfo.EnvironmentVariables["HTTPS_PROXY"] = this.proxy.Address; - startInfo.EnvironmentVariables["SSL_CERT_FILE"] = this.proxy.CertificatePath; - } + proxy?.SetProcessEnvironment(startInfo); return startInfo; } diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IRegistryProxy.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IRegistryProxy.cs index 2537ac0b4054..a7f7c1567152 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IRegistryProxy.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/IRegistryProxy.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Immutable; +using System.Diagnostics; using System.Security.Cryptography.X509Certificates; namespace Semmle.Extraction.CSharp.DependencyFetching @@ -30,5 +31,11 @@ public interface IRegistryProxy : IDisposable /// The certificate used for the registry proxy. /// X509Certificate2? Certificate { get; } + + /// + /// Configures the environment variables for a process to use the registry proxy. + /// + /// The process start info to configure. + void SetProcessEnvironment(ProcessStartInfo pi); } } diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/RegistryProxy.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/RegistryProxy.cs index e16ea66725c7..b6f1cbdd1d5d 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/RegistryProxy.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/RegistryProxy.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Immutable; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Security.Cryptography.X509Certificates; using Semmle.Util; @@ -36,6 +37,8 @@ public class RegistryConfig public string Address { get; } + private readonly ILogger logger; + /// /// A dictionary mapping registry URLs to a boolean indicating whether they replace the base registry. /// @@ -65,8 +68,9 @@ public class RegistryConfig public X509Certificate2? Certificate { get; private set; } - private RegistryProxy(IRegistryProxyConfiguration config, ILogger logger, TemporaryDirectory tempWorkingDirectory) + private RegistryProxy(IRegistryProxyConfiguration config, ILogger l, TemporaryDirectory tempWorkingDirectory) { + logger = l; Address = $"http://{config.Host}:{config.Port}"; if (!string.IsNullOrWhiteSpace(config.Certificate)) @@ -179,6 +183,22 @@ private RegistryProxy(IRegistryProxyConfiguration config, ILogger logger, Tempor return result; } + public void SetProcessEnvironment(ProcessStartInfo pi) + { + logger.LogDebug($"Configuring environment variables for the registry proxy at {Address}"); + + pi.EnvironmentVariables["HTTP_PROXY"] = Address; + pi.EnvironmentVariables["HTTPS_PROXY"] = Address; + if (CertificatePath != null) + { + pi.EnvironmentVariables["SSL_CERT_FILE"] = CertificatePath; + } + else + { + logger.LogDebug("No SSL certificate is configured for the registry proxy."); + } + } + public void Dispose() { Certificate?.Dispose(); diff --git a/csharp/extractor/Semmle.Extraction.Tests/FeedManager.cs b/csharp/extractor/Semmle.Extraction.Tests/FeedManager.cs index d812f008c43b..ae97d2e7e346 100644 --- a/csharp/extractor/Semmle.Extraction.Tests/FeedManager.cs +++ b/csharp/extractor/Semmle.Extraction.Tests/FeedManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.IO; using System.Linq; using System.Security.Cryptography.X509Certificates; @@ -17,6 +18,8 @@ public class RegistryProxyStub : IRegistryProxy public string? CertificatePath { get; } = null; public X509Certificate2? Certificate { get; } = null; + public void SetProcessEnvironment(ProcessStartInfo pi) { } + public void Dispose() { } } @@ -28,6 +31,7 @@ public class RegistryProxyStubWithBaseUrls : IRegistryProxy public string? CertificatePath { get; } = null; public X509Certificate2? Certificate { get; } = null; + public void SetProcessEnvironment(ProcessStartInfo pi) { } public void Dispose() { } } diff --git a/csharp/extractor/Semmle.Extraction.Tests/RegistryProxy.cs b/csharp/extractor/Semmle.Extraction.Tests/RegistryProxy.cs index 5705077c0219..c2d9ef2d9d39 100644 --- a/csharp/extractor/Semmle.Extraction.Tests/RegistryProxy.cs +++ b/csharp/extractor/Semmle.Extraction.Tests/RegistryProxy.cs @@ -1,5 +1,6 @@ using Xunit; using System; +using System.Diagnostics; using System.IO; using Semmle.Extraction.CSharp.DependencyFetching; using Semmle.Util; @@ -245,5 +246,38 @@ public void TestRegistryProxyUrlsReplacesBase() "https://example.com/org/index.json", ], proxy.RegistryBaseURLs); } + + /// + /// Verifies that the registry proxy correctly sets the environment variables needed for a .NET process to use the proxy. + /// In this case, the environment variables for the HTTP and HTTPS proxies, as well as the SSL certificate file, should be correctly set. + /// The http proxies should be set to the proxy address, and the SSL certificate file should point to the certificate path. + /// The latter is tested by verifying that the SSL_CERT_FILE environment variable ends with "proxy.crt" as we can't check the absolute path + /// due to temporary directories. + /// + [Fact] + public void TestRegistryProxyProcessEnvironment() + { + // Setup + var config = new RegistryConfigurationStub + { + Port = "8080", + Host = "localhost", + Certificate = ExampleCertificate + }; + + // Execute + using var tempWorkingDirectory = MakeTemporaryDirectory(); + using var proxy = RegistryProxy.Make(config, new LoggerStub(), new DiagnosticsWriterStub(), tempWorkingDirectory); + + var pi = new ProcessStartInfo("nuget"); + proxy?.SetProcessEnvironment(pi); + + // Verify + Assert.NotNull(proxy); + Assert.Equal("http://localhost:8080", proxy.Address); + Assert.Equal("http://localhost:8080", pi.EnvironmentVariables["HTTP_PROXY"]); + Assert.Equal("http://localhost:8080", pi.EnvironmentVariables["HTTPS_PROXY"]); + Assert.EndsWith("proxy.crt", pi.EnvironmentVariables["SSL_CERT_FILE"]); + } } } From 2f3f6dfc991e2708509d56ec964acf653b1985de Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Mon, 5 Oct 2026 15:29:20 +0200 Subject: [PATCH 2/6] C#: Set proxy environment variables for sub-process invoking the NuGet CLI. --- .../NugetPackageRestorer.cs | 4 +++- .../PackagesConfigRestorer.cs | 11 ++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index fee72034781f..9257edfe2c19 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -18,6 +18,7 @@ internal sealed partial class NugetPackageRestorer : IDisposable private readonly IFileProvider fileProvider; private readonly FileContent fileContent; private readonly IDotNet dotnet; + private readonly IRegistryProxy? registryProxy; private readonly IDiagnosticsWriter diagnosticsWriter; private readonly DependencyDirectory legacyPackageDirectory; private readonly DependencyDirectory missingPackageDirectory; @@ -40,6 +41,7 @@ public NugetPackageRestorer( this.fileProvider = fileProvider; this.fileContent = fileContent; this.dotnet = dotnet; + this.registryProxy = registryProxy; this.diagnosticsWriter = diagnosticsWriter; this.logger = logger; this.compilationInfoContainer = compilationInfoContainer; @@ -133,7 +135,7 @@ public HashSet Restore() try { - var packagesConfigRestore = PackagesConfigRestoreFactory.Create(fileProvider, legacyPackageDirectory, logger, feedManager); + var packagesConfigRestore = PackagesConfigRestoreFactory.Create(fileProvider, legacyPackageDirectory, logger, feedManager, registryProxy); var count = packagesConfigRestore.InstallPackages(); if (packagesConfigRestore.PackageCount > 0) { diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs index 861622ca4c02..c111ca017b2b 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs @@ -33,11 +33,11 @@ internal interface IPackagesConfigRestore /// internal class PackagesConfigRestoreFactory { - public static IPackagesConfigRestore Create(IFileProvider fileProvider, DependencyDirectory packageDirectory, Semmle.Util.Logging.ILogger logger, FeedManager feedManager) + public static IPackagesConfigRestore Create(IFileProvider fileProvider, DependencyDirectory packageDirectory, Semmle.Util.Logging.ILogger logger, FeedManager feedManager, IRegistryProxy? registryProxy) { if (SystemBuildActions.Instance.IsWindows() || SystemBuildActions.Instance.IsMonoInstalled()) { - return new NugetExeWrapper(fileProvider, packageDirectory, logger, feedManager); + return new NugetExeWrapper(fileProvider, packageDirectory, logger, feedManager, registryProxy); } return new NoOpPackagesConfig(fileProvider.PackagesConfigs, logger); @@ -52,6 +52,7 @@ private class NugetExeWrapper : IPackagesConfigRestore { private readonly string? nugetExe; private readonly Semmle.Util.Logging.ILogger logger; + private readonly IRegistryProxy? registryProxy; public int PackageCount => fileProvider.PackagesConfigs.Count; @@ -70,12 +71,13 @@ private class NugetExeWrapper : IPackagesConfigRestore /// /// Create the package manager for a specified source tree. /// - public NugetExeWrapper(IFileProvider fileProvider, DependencyDirectory packageDirectory, Semmle.Util.Logging.ILogger logger, FeedManager feedManager) + public NugetExeWrapper(IFileProvider fileProvider, DependencyDirectory packageDirectory, Semmle.Util.Logging.ILogger logger, FeedManager feedManager, IRegistryProxy? registryProxy) { this.fileProvider = fileProvider; this.packageDirectory = packageDirectory; this.logger = logger; this.feedManager = feedManager; + this.registryProxy = registryProxy; if (fileProvider.PackagesConfigs.Count > 0) { @@ -209,6 +211,9 @@ private bool TryRestoreNugetPackage(string packagesConfig) UseShellExecute = false }; + // Configure the proxy settings, if applicable. + registryProxy?.SetProcessEnvironment(pi); + var threadId = Environment.CurrentManagedThreadId; void onOut(string s) => logger.LogDebug(s, threadId); void onError(string s) => logger.LogError(s, threadId); From 36395a36907071bc88187c6c442bc8d36f51f936 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Tue, 6 Oct 2026 11:30:53 +0200 Subject: [PATCH 3/6] C#: Also set lower case versions of the proxy environment variables. --- .../RegistryProxy.cs | 6 ++++++ csharp/extractor/Semmle.Extraction.Tests/RegistryProxy.cs | 2 ++ 2 files changed, 8 insertions(+) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/RegistryProxy.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/RegistryProxy.cs index b6f1cbdd1d5d..dea90849e063 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/RegistryProxy.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/RegistryProxy.cs @@ -189,6 +189,12 @@ public void SetProcessEnvironment(ProcessStartInfo pi) pi.EnvironmentVariables["HTTP_PROXY"] = Address; pi.EnvironmentVariables["HTTPS_PROXY"] = Address; + + // Also set the lower case variants of the environment variables + // This might be needed on Linux systems. + pi.EnvironmentVariables["http_proxy"] = Address; + pi.EnvironmentVariables["https_proxy"] = Address; + if (CertificatePath != null) { pi.EnvironmentVariables["SSL_CERT_FILE"] = CertificatePath; diff --git a/csharp/extractor/Semmle.Extraction.Tests/RegistryProxy.cs b/csharp/extractor/Semmle.Extraction.Tests/RegistryProxy.cs index c2d9ef2d9d39..f978736416fd 100644 --- a/csharp/extractor/Semmle.Extraction.Tests/RegistryProxy.cs +++ b/csharp/extractor/Semmle.Extraction.Tests/RegistryProxy.cs @@ -277,6 +277,8 @@ public void TestRegistryProxyProcessEnvironment() Assert.Equal("http://localhost:8080", proxy.Address); Assert.Equal("http://localhost:8080", pi.EnvironmentVariables["HTTP_PROXY"]); Assert.Equal("http://localhost:8080", pi.EnvironmentVariables["HTTPS_PROXY"]); + Assert.Equal("http://localhost:8080", pi.EnvironmentVariables["http_proxy"]); + Assert.Equal("http://localhost:8080", pi.EnvironmentVariables["https_proxy"]); Assert.EndsWith("proxy.crt", pi.EnvironmentVariables["SSL_CERT_FILE"]); } } From 983d9361567bd90157de50d554a1c7c23ec217ab Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Tue, 6 Oct 2026 09:54:06 +0200 Subject: [PATCH 4/6] C#: Log the NuGet CLI command before executing it. --- .../PackagesConfigRestorer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs index c111ca017b2b..29276d72a82c 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/PackagesConfigRestorer.cs @@ -217,6 +217,7 @@ private bool TryRestoreNugetPackage(string packagesConfig) var threadId = Environment.CurrentManagedThreadId; void onOut(string s) => logger.LogDebug(s, threadId); void onError(string s) => logger.LogError(s, threadId); + logger.LogInfo($"Running '{pi.FileName} {string.Join(" ", pi.ArgumentList)}'"); var exitCode = pi.ReadOutput(out _, onOut, onError); if (exitCode != 0) { From 16ebfc88a52ee3f2dc3db7552fe329525e8a69da Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Tue, 6 Oct 2026 09:54:27 +0200 Subject: [PATCH 5/6] C#: Add change-note. --- .../ql/lib/change-notes/2026-10-05-nugetcli-proxy-config.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 csharp/ql/lib/change-notes/2026-10-05-nugetcli-proxy-config.md diff --git a/csharp/ql/lib/change-notes/2026-10-05-nugetcli-proxy-config.md b/csharp/ql/lib/change-notes/2026-10-05-nugetcli-proxy-config.md new file mode 100644 index 000000000000..a953486e70ea --- /dev/null +++ b/csharp/ql/lib/change-notes/2026-10-05-nugetcli-proxy-config.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Proxy and certificate environment variables are now set for the subprocess that invokes the NuGet CLI, enabling it to access private registries during this part of the workflow. From 2e0f0c4bd56169611c28eeb4665145a89457af38 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Tue, 6 Oct 2026 14:13:43 +0200 Subject: [PATCH 6/6] C#: Address review comments. --- .../RegistryProxy.cs | 5 +---- .../ql/lib/change-notes/2026-10-05-nugetcli-proxy-config.md | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/RegistryProxy.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/RegistryProxy.cs index dea90849e063..d49d08510a22 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/RegistryProxy.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/RegistryProxy.cs @@ -197,12 +197,9 @@ public void SetProcessEnvironment(ProcessStartInfo pi) if (CertificatePath != null) { + logger.LogDebug("Setting the SSL certificate path for the registry proxy."); pi.EnvironmentVariables["SSL_CERT_FILE"] = CertificatePath; } - else - { - logger.LogDebug("No SSL certificate is configured for the registry proxy."); - } } public void Dispose() diff --git a/csharp/ql/lib/change-notes/2026-10-05-nugetcli-proxy-config.md b/csharp/ql/lib/change-notes/2026-10-05-nugetcli-proxy-config.md index a953486e70ea..3f2ed8cddb56 100644 --- a/csharp/ql/lib/change-notes/2026-10-05-nugetcli-proxy-config.md +++ b/csharp/ql/lib/change-notes/2026-10-05-nugetcli-proxy-config.md @@ -1,4 +1,4 @@ --- category: minorAnalysis --- -* Proxy and certificate environment variables are now set for the subprocess that invokes the NuGet CLI, enabling it to access private registries during this part of the workflow. +* The subprocess for the NuGet CLI is now provided with the proxy and certificate environment variables needed to access private registries if any are configured.