From 2aa0445cdcb00a6cd9e7377b49231014c9d679f2 Mon Sep 17 00:00:00 2001 From: Waldek Mastykarz Date: Thu, 5 Nov 2026 11:44:12 +0100 Subject: [PATCH] Don't register as system proxy or install root cert by default in CI When the CI environment variable is set, asSystemProxy and installCert default to false unless set explicitly in the config file or on the command line. Dev Proxy logs the effective values and how to override them at startup. Closes dotnet/dev-proxy#2059 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../ProxyConfigurationCiDefaultsTests.cs | 95 +++++++++++++++++++ DevProxy/Commands/DevProxyCommand.cs | 25 ++++- DevProxy/Proxy/ProxyConfiguration.cs | 33 ++++++- .../dev-proxy/references/ci-cd-integration.md | 2 + skills/dev-proxy/references/configuration.md | 3 +- 5 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 DevProxy.Tests/ProxyConfigurationCiDefaultsTests.cs diff --git a/DevProxy.Tests/ProxyConfigurationCiDefaultsTests.cs b/DevProxy.Tests/ProxyConfigurationCiDefaultsTests.cs new file mode 100644 index 00000000..70baded2 --- /dev/null +++ b/DevProxy.Tests/ProxyConfigurationCiDefaultsTests.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using DevProxy.Commands; +using DevProxy.Proxy; +using Microsoft.Extensions.Configuration; +using Xunit; + +namespace DevProxy.Tests; + +public class ProxyConfigurationCiDefaultsTests +{ + private static ProxyConfiguration Create(bool isCi, Dictionary? settings = null) + { + var configurationRoot = new ConfigurationBuilder() + .AddInMemoryCollection(settings ?? []) + .Build(); + return new ProxyConfiguration(configurationRoot, isCi); + } + + [Fact] + public void NotCi_KeepsDefaults() + { + var config = Create(isCi: false); + + Assert.True(config.AsSystemProxy); + Assert.True(config.InstallCert); + Assert.False(config.AsSystemProxyDefaultedForCi); + Assert.False(config.InstallCertDefaultedForCi); + } + + [Fact] + public void Ci_NotSetInConfig_DisablesSystemProxyAndCertInstall() + { + var config = Create(isCi: true); + + Assert.False(config.AsSystemProxy); + Assert.False(config.InstallCert); + Assert.True(config.AsSystemProxyDefaultedForCi); + Assert.True(config.InstallCertDefaultedForCi); + } + + [Theory] + [InlineData("true", true)] + [InlineData("false", false)] + public void Ci_SetInConfig_KeepsConfiguredValues(string value, bool expected) + { + var config = Create(isCi: true, new() + { + ["asSystemProxy"] = value, + ["installCert"] = value, + }); + + Assert.Equal(expected, config.AsSystemProxy); + Assert.Equal(expected, config.InstallCert); + Assert.False(config.AsSystemProxyDefaultedForCi); + Assert.False(config.InstallCertDefaultedForCi); + } + + [Fact] + public void Ci_OnlyAsSystemProxyInConfig_DefaultsInstallCertOnly() + { + var config = Create(isCi: true, new() { ["asSystemProxy"] = "true" }); + + Assert.True(config.AsSystemProxy); + Assert.False(config.InstallCert); + Assert.False(config.AsSystemProxyDefaultedForCi); + Assert.True(config.InstallCertDefaultedForCi); + } + + [Fact] + public void ShouldLogCiDefaults_NotCi_ReturnsFalse() + { + Assert.False(DevProxyCommand.ShouldLogCiDefaults(Create(isCi: false), null, null)); + } + + [Fact] + public void ShouldLogCiDefaults_CiDefaultsApplied_ReturnsTrue() + { + Assert.True(DevProxyCommand.ShouldLogCiDefaults(Create(isCi: true), null, null)); + } + + [Fact] + public void ShouldLogCiDefaults_OneOverriddenOnCommandLine_ReturnsTrue() + { + Assert.True(DevProxyCommand.ShouldLogCiDefaults(Create(isCi: true), true, null)); + } + + [Fact] + public void ShouldLogCiDefaults_BothOverriddenOnCommandLine_ReturnsFalse() + { + Assert.False(DevProxyCommand.ShouldLogCiDefaults(Create(isCi: true), true, true)); + } +} diff --git a/DevProxy/Commands/DevProxyCommand.cs b/DevProxy/Commands/DevProxyCommand.cs index 6577c0bc..454517a6 100644 --- a/DevProxy/Commands/DevProxyCommand.cs +++ b/DevProxy/Commands/DevProxyCommand.cs @@ -490,8 +490,8 @@ private void ConfigureCommand(DevProxyConfigOptions configOptions) { try { - var asSystemProxy = input.GetValue(asSystemProxyOption) ?? true; - var installCert = input.GetValue(installCertOption) ?? true; + var asSystemProxy = input.GetValue(asSystemProxyOption) ?? _proxyConfiguration.AsSystemProxy; + var installCert = input.GetValue(installCertOption) ?? _proxyConfiguration.InstallCert; if (asSystemProxy && !installCert) { input.AddError($"Requires option '{AsSystemProxyOptionName}' to be 'false'"); @@ -746,6 +746,7 @@ private void ConfigureFromOptions(ParseResult parseResult) { _proxyConfiguration.InstallCert = installCert.Value; } + LogCiDefaults(asSystemProxy, installCert); var timeout = parseResult.GetValueOrDefault(TimeoutOptionName); if (timeout is not null) { @@ -771,6 +772,26 @@ private void ConfigureFromOptions(ParseResult parseResult) } } + private void LogCiDefaults(bool? asSystemProxyOption, bool? installCertOption) + { + if (_proxyConfiguration is not ProxyConfiguration proxyConfiguration || + !ShouldLogCiDefaults(proxyConfiguration, asSystemProxyOption, installCertOption)) + { + return; + } + + _logger.LogInformation( + "CI environment detected. Using asSystemProxy: {AsSystemProxy}, installCert: {InstallCert}. To change, use {AsSystemProxyOption} and {InstallCertOption} or set asSystemProxy and installCert in the config file.", + proxyConfiguration.AsSystemProxy.ToString().ToLowerInvariant(), + proxyConfiguration.InstallCert.ToString().ToLowerInvariant(), + AsSystemProxyOptionName, + InstallCertOptionName); + } + + internal static bool ShouldLogCiDefaults(ProxyConfiguration proxyConfiguration, bool? asSystemProxyOption, bool? installCertOption) => + (proxyConfiguration.AsSystemProxyDefaultedForCi && asSystemProxyOption is null) || + (proxyConfiguration.InstallCertDefaultedForCi && installCertOption is null); + private async Task CheckForNewVersionAsync() { var newReleaseInfo = await _updateNotification.CheckForNewVersionAsync(_proxyConfiguration.NewVersionNotification); diff --git a/DevProxy/Proxy/ProxyConfiguration.cs b/DevProxy/Proxy/ProxyConfiguration.cs index 27b65c73..2f53f310 100755 --- a/DevProxy/Proxy/ProxyConfiguration.cs +++ b/DevProxy/Proxy/ProxyConfiguration.cs @@ -53,10 +53,41 @@ public string ConfigFile public IEnumerable WatchProcessNames { get; set; } = []; public bool WatchProcessTree { get; set; } - public ProxyConfiguration(IConfigurationRoot configurationRoot) + // True when the value wasn't set in the config file and was turned off + // because Dev Proxy is running in CI. + internal bool AsSystemProxyDefaultedForCi { get; private set; } + internal bool InstallCertDefaultedForCi { get; private set; } + + internal static bool IsCi => Environment.GetEnvironmentVariable("CI") is not null; + + public ProxyConfiguration(IConfigurationRoot configurationRoot) : this(configurationRoot, IsCi) + { + } + + internal ProxyConfiguration(IConfigurationRoot configurationRoot, bool isCi) { _configurationRoot = configurationRoot; _configurationRoot.Bind(this); + if (isCi) + { + ApplyCiDefaults(_configurationRoot); + } + } + + // In CI, don't change machine-wide state (system proxy, trusted root + // certificates) unless the config file explicitly asks for it. + private void ApplyCiDefaults(IConfiguration configuration) + { + if (configuration[nameof(AsSystemProxy)] is null) + { + AsSystemProxy = false; + AsSystemProxyDefaultedForCi = true; + } + if (configuration[nameof(InstallCert)] is null) + { + InstallCert = false; + InstallCertDefaultedForCi = true; + } } internal static string GetConfigFilePath(IConfigurationRoot configurationRoot) diff --git a/skills/dev-proxy/references/ci-cd-integration.md b/skills/dev-proxy/references/ci-cd-integration.md index f3a98d22..b0c01159 100644 --- a/skills/dev-proxy/references/ci-cd-integration.md +++ b/skills/dev-proxy/references/ci-cd-integration.md @@ -4,6 +4,8 @@ Dev Proxy runs headlessly in CI/CD pipelines to automate API testing — catchin With Dev Proxy 3.3.1 or later, every management API request requires a bearer token. Set `CI=true` in the proxy process to suppress automatic token output, and retrieve credentials explicitly with `devproxy api token`. Keep shell tracing disabled around credentials. For multiple instances, add `--pid ` and use the actual API URL from `devproxy status`. +When `CI` is set, Dev Proxy doesn't register itself as the system proxy or install its root certificate, unless you set `asSystemProxy` or `installCert` in the config file or pass `--as-system-proxy` or `--install-cert`. Point your app at Dev Proxy with `HTTPS_PROXY`/`HTTP_PROXY` and trust the root certificate as shown below. + Use action versions that support the authenticated API. For integrations that have not yet added bearer authentication, use the CLI and authenticated HTTP examples below. ## GitHub Actions (Recommended) diff --git a/skills/dev-proxy/references/configuration.md b/skills/dev-proxy/references/configuration.md index 7cbd2222..75aabdb2 100644 --- a/skills/dev-proxy/references/configuration.md +++ b/skills/dev-proxy/references/configuration.md @@ -136,7 +136,8 @@ Plugin-specific override: | `watchPids` | — | `--watch-pids` | Only intercept from these PIDs | | `watchProcessNames` | — | `--watch-process-names` | Only intercept from these processes | | `watchProcessTree` | — | `--watch-process-tree` | Also intercept descendants of watched processes | -| `asSystemProxy` | `true` | `--as-system-proxy` | Register as system proxy | +| `asSystemProxy` | `true` (`false` when `CI` is set) | `--as-system-proxy` | Register as system proxy | +| `installCert` | `true` (`false` when `CI` is set) | `--install-cert` | Install and trust the root certificate | | — | off | `--detach` | Run in detached (background) mode | | — | `text` | `--output` | Output format: `text` or `json` |