@@ -30,13 +30,16 @@ sealed class ProxyEngine(
3030 IProxyConfiguration proxyConfiguration ,
3131 ISet < UrlToWatch > urlsToWatch ,
3232 IProxyStateController proxyController ,
33- ILogger < ProxyEngine > logger ) : BackgroundService , IDisposable
33+ ILogger < ProxyEngine > logger ,
34+ ILoggerFactory loggerFactory ) : BackgroundService , IDisposable
3435{
3536 private readonly IEnumerable < IPlugin > _plugins = plugins ;
3637 private readonly ILogger _logger = logger ;
3738 private readonly IProxyConfiguration _config = proxyConfiguration ;
3839
39- internal static ProxyServer ProxyServer { get ; private set ; }
40+ internal static ProxyServer ProxyServer { get ; private set ; } = null ! ;
41+ private static bool _isProxyServerInitialized ;
42+ private static readonly object _initLock = new ( ) ;
4043 private ExplicitProxyEndPoint ? _explicitEndPoint ;
4144 // lists of URLs to watch, used for intercepting requests
4245 private readonly ISet < UrlToWatch > _urlsToWatch = urlsToWatch ;
@@ -56,23 +59,53 @@ sealed class ProxyEngine(
5659
5760 static ProxyEngine ( )
5861 {
59- ProxyServer = new ( ) ;
60- ProxyServer . CertificateManager . PfxFilePath = Environment . GetEnvironmentVariable ( "DEV_PROXY_CERT_PATH" ) ?? string . Empty ;
61- ProxyServer . CertificateManager . RootCertificateName = "Dev Proxy CA" ;
62- ProxyServer . CertificateManager . CertificateStorage = new CertificateDiskCache ( ) ;
63- // we need to change this to a value lower than 397
64- // to avoid the ERR_CERT_VALIDITY_TOO_LONG error in Edge
65- ProxyServer . CertificateManager . CertificateValidDays = 365 ;
66-
67- using var joinableTaskContext = new JoinableTaskContext ( ) ;
68- var joinableTaskFactory = new JoinableTaskFactory ( joinableTaskContext ) ;
69- _ = joinableTaskFactory . Run ( async ( ) => await ProxyServer . CertificateManager . LoadOrCreateRootCertificateAsync ( ) ) ;
62+ // ProxyServer initialization moved to EnsureProxyServerInitialized
63+ // to enable passing ILoggerFactory for Unobtanium logging
64+ }
65+
66+ // Ensure ProxyServer is initialized with the given ILoggerFactory
67+ // This method can be called from multiple places (ProxyEngine, CertCommand, etc.)
68+ internal static void EnsureProxyServerInitialized ( ILoggerFactory ? loggerFactory = null )
69+ {
70+ if ( _isProxyServerInitialized )
71+ {
72+ return ;
73+ }
74+
75+ lock ( _initLock )
76+ {
77+ if ( _isProxyServerInitialized )
78+ {
79+ return ;
80+ }
81+
82+ // On macOS/Linux, don't let Unobtanium try to install the cert
83+ // in the Root store via .NET's X509Store API — it requires admin
84+ // privileges and fails with "Access is denied".
85+ // On macOS, Dev Proxy handles trust via MacCertificateHelper instead.
86+ ProxyServer = new ( userTrustRootCertificate : RunTime . IsWindows , loggerFactory : loggerFactory ) ;
87+ ProxyServer . CertificateManager . PfxFilePath = Environment . GetEnvironmentVariable ( "DEV_PROXY_CERT_PATH" ) ?? string . Empty ;
88+ ProxyServer . CertificateManager . RootCertificateName = "Dev Proxy CA" ;
89+ ProxyServer . CertificateManager . CertificateStorage = new CertificateDiskCache ( ) ;
90+ // we need to change this to a value lower than 397
91+ // to avoid the ERR_CERT_VALIDITY_TOO_LONG error in Edge
92+ ProxyServer . CertificateManager . CertificateValidDays = 365 ;
93+
94+ using var joinableTaskContext = new JoinableTaskContext ( ) ;
95+ var joinableTaskFactory = new JoinableTaskFactory ( joinableTaskContext ) ;
96+ _ = joinableTaskFactory . Run ( async ( ) => await ProxyServer . CertificateManager . LoadOrCreateRootCertificateAsync ( ) ) ;
97+
98+ _isProxyServerInitialized = true ;
99+ }
70100 }
71101
72102 protected override async Task ExecuteAsync ( CancellationToken stoppingToken )
73103 {
74104 _cancellationToken = stoppingToken ;
75105
106+ // Initialize ProxyServer with LoggerFactory for Unobtanium logging
107+ EnsureProxyServerInitialized ( loggerFactory ) ;
108+
76109 Debug . Assert ( ProxyServer is not null , "Proxy server is not initialized" ) ;
77110
78111 if ( ! _urlsToWatch . Any ( ) )
@@ -188,18 +221,26 @@ private void FirstRunSetup()
188221 return ;
189222 }
190223
191- var bashScriptPath = Path . Join ( ProxyUtils . AppFolder , "trust-cert.sh" ) ;
192- ProcessStartInfo startInfo = new ( )
224+ Console . WriteLine ( ) ;
225+ Console . WriteLine ( "Dev Proxy uses a self-signed certificate to intercept and inspect HTTPS traffic." ) ;
226+ Console . Write ( "Update the certificate in your Keychain so that it's trusted by your browser? (Y/n): " ) ;
227+ var answer = Console . ReadLine ( ) ? . Trim ( ) ;
228+
229+ if ( string . Equals ( answer , "n" , StringComparison . OrdinalIgnoreCase ) )
193230 {
194- FileName = "/bin/bash" ,
195- Arguments = bashScriptPath ,
196- UseShellExecute = true ,
197- CreateNoWindow = false
198- } ;
231+ _logger . LogWarning ( "Trust the certificate in your Keychain manually to avoid errors." ) ;
232+ return ;
233+ }
199234
200- using var process = new Process ( ) { StartInfo = startInfo } ;
201- _ = process . Start ( ) ;
202- process . WaitForExit ( ) ;
235+ var certificate = ProxyServer . CertificateManager . RootCertificate ;
236+ if ( certificate is null )
237+ {
238+ _logger . LogError ( "Root certificate not found. Cannot trust certificate." ) ;
239+ return ;
240+ }
241+
242+ MacCertificateHelper . TrustCertificate ( certificate , _logger ) ;
243+ _logger . LogInformation ( "Certificate trusted successfully." ) ;
203244 }
204245
205246 private async Task ReadKeysAsync ( CancellationToken cancellationToken )
0 commit comments