@@ -64,6 +64,9 @@ async function publishCoverage(inputFiles: string[], reportDirectory: string, pa
6464 }
6565
6666 try {
67+ // Get comprehensive proxy configuration to fix .NET HttpClient proxy issues
68+ const proxyConfig = getProxyEnvironmentVariables ( ) ;
69+
6770 const env = {
6871 "SYSTEM_ACCESSTOKEN" : taskLib . getEndpointAuthorizationParameter ( 'SystemVssConnection' , 'AccessToken' , false ) ,
6972 "SYSTEM_TEAMFOUNDATIONCOLLECTIONURI" : taskLib . getVariable ( 'System.TeamFoundationCollectionUri' ) ,
@@ -72,9 +75,9 @@ async function publishCoverage(inputFiles: string[], reportDirectory: string, pa
7275 "AGENT_TEMPPATH" : taskLib . getVariable ( 'Agent.TempPath' ) ,
7376 "SYSTEM_TEAMPROJECTID" : taskLib . getVariable ( 'System.TeamProjectId' ) ,
7477 "PIPELINES_COVERAGEPUBLISHER_DEBUG" : taskLib . getVariable ( 'PIPELINES_COVERAGEPUBLISHER_DEBUG' ) ,
75- "HTTPS_PROXY " : process . env [ 'HTTPS_PROXY' ] ,
76- "NO_PROXY" : process . env [ 'NO_PROXY' ] ,
77- "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT" : taskLib . getVariable ( 'DOTNET_SYSTEM_GLOBALIZATION_INVARIANT' )
78+ "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT " : taskLib . getVariable ( 'DOTNET_SYSTEM_GLOBALIZATION_INVARIANT' ) ,
79+ // Comprehensive proxy configuration for .NET HttpClient
80+ ... proxyConfig
7881 } ;
7982
8083 await dotnet . exec ( {
@@ -96,6 +99,132 @@ async function publishCoverage(inputFiles: string[], reportDirectory: string, pa
9699}
97100
98101
102+ function getProxyEnvironmentVariables ( ) : { [ key : string ] : string } {
103+ const proxyVars : { [ key : string ] : string } = { } ;
104+
105+ // Get Azure Pipelines agent proxy configuration (highest priority)
106+ const agentProxyUrl = taskLib . getVariable ( "agent.proxyurl" ) ;
107+ const agentProxyUsername = taskLib . getVariable ( "agent.proxyusername" ) ;
108+ const agentProxyPassword = taskLib . getVariable ( "agent.proxypassword" ) ;
109+ const agentProxyBypass = taskLib . getVariable ( "agent.proxybypasslist" ) ;
110+
111+ // Input validation and sanitization
112+ if ( agentProxyUrl && ! isValidProxyUrl ( agentProxyUrl ) ) {
113+ taskLib . warning ( "Invalid proxy URL format detected, skipping agent proxy configuration" ) ;
114+ return proxyVars ;
115+ }
116+
117+ // Construct proxy URL with authentication if available
118+ let proxyUrl = "" ;
119+ if ( agentProxyUrl ) {
120+ if ( agentProxyUsername && agentProxyPassword ) {
121+ // Validate credentials contain no malicious characters
122+ if ( ! isValidCredential ( agentProxyUsername ) || ! isValidCredential ( agentProxyPassword ) ) {
123+ taskLib . warning ( "Invalid characters detected in proxy credentials, using proxy without authentication" ) ;
124+ proxyUrl = agentProxyUrl ;
125+ } else {
126+ // Add authentication to proxy URL
127+ try {
128+ const url = new URL ( agentProxyUrl ) ;
129+ url . username = encodeURIComponent ( agentProxyUsername ) ;
130+ url . password = encodeURIComponent ( agentProxyPassword ) ;
131+ proxyUrl = url . toString ( ) ;
132+ taskLib . debug ( `Using agent proxy with authentication: ${ getMaskedProxyUrl ( proxyUrl ) } ` ) ;
133+ } catch ( err ) {
134+ proxyUrl = agentProxyUrl ;
135+ taskLib . warning ( `Failed to parse agent proxy URL, using without authentication: ${ err } ` ) ;
136+ }
137+ }
138+ } else {
139+ proxyUrl = agentProxyUrl ;
140+ taskLib . debug ( `Using agent proxy: ${ getMaskedProxyUrl ( agentProxyUrl ) } ` ) ;
141+ }
142+ } else {
143+ // Fall back to environment variables
144+ proxyUrl = process . env [ 'HTTPS_PROXY' ] || process . env [ 'https_proxy' ] ||
145+ process . env [ 'HTTP_PROXY' ] || process . env [ 'http_proxy' ] || "" ;
146+ if ( proxyUrl ) {
147+ taskLib . debug ( `Using environment proxy: ${ getMaskedProxyUrl ( proxyUrl ) } ` ) ;
148+ }
149+ }
150+ // Set comprehensive proxy environment variables for .NET
151+ if ( proxyUrl ) {
152+ // Standard proxy environment variables (case variations for compatibility)
153+ proxyVars [ "HTTP_PROXY" ] = proxyUrl ;
154+ proxyVars [ "HTTPS_PROXY" ] = proxyUrl ;
155+ proxyVars [ "http_proxy" ] = proxyUrl ;
156+ proxyVars [ "https_proxy" ] = proxyUrl ;
157+
158+ // .NET specific environment variables to force proxy initialization
159+ // These are critical for resolving the HttpClient.DefaultProxy initialization issue
160+ proxyVars [ "DOTNET_SYSTEM_NET_HTTP_USEDEFAULTCREDENTIALS" ] = "true" ;
161+ proxyVars [ "DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER" ] = "false" ; // Forces WinHttpHandler on Windows
162+ }
163+
164+ // Handle NO_PROXY / bypass list
165+ let noProxyList = "" ;
166+ if ( agentProxyBypass ) {
167+ noProxyList = agentProxyBypass ;
168+ taskLib . debug ( `Using agent proxy bypass list: ${ agentProxyBypass } ` ) ;
169+ } else {
170+ noProxyList = process . env [ 'NO_PROXY' ] || process . env [ 'no_proxy' ] || "" ;
171+ if ( noProxyList ) {
172+ taskLib . debug ( `Using environment proxy bypass list: ${ noProxyList } ` ) ;
173+ }
174+ }
175+
176+ if ( noProxyList ) {
177+ proxyVars [ "NO_PROXY" ] = noProxyList ;
178+ proxyVars [ "no_proxy" ] = noProxyList ;
179+ }
180+
181+ // Log configuration for debugging (with enhanced credential masking)
182+ if ( proxyUrl ) {
183+ taskLib . debug ( `Proxy configuration set for .NET application: ${ getMaskedProxyUrl ( proxyUrl ) } ` ) ;
184+ if ( noProxyList ) {
185+ taskLib . debug ( `Proxy bypass list: ${ noProxyList } ` ) ;
186+ }
187+ } else {
188+ taskLib . debug ( "No proxy configuration detected" ) ;
189+ }
190+
191+ // Security note: Environment variables will contain proxy credentials
192+ // Ensure child process cleans up these variables after use
193+ return proxyVars ;
194+ }
195+
196+ // Security helper functions
197+ function isValidProxyUrl ( url : string ) : boolean {
198+ try {
199+ const parsedUrl = new URL ( url ) ;
200+ return [ 'http:' , 'https:' ] . includes ( parsedUrl . protocol ) ;
201+ } catch {
202+ return false ;
203+ }
204+ }
205+
206+ function isValidCredential ( credential : string ) : boolean {
207+ // Basic validation to prevent injection attacks
208+ // Reject credentials containing potentially dangerous characters
209+ const dangerousChars = / [ \r \n \0 \x1f < > " ' ` \\ ] / ;
210+ return ! dangerousChars . test ( credential ) && credential . length <= 256 ;
211+ }
212+
213+ function getMaskedProxyUrl ( url : string ) : string {
214+ if ( ! url ) return url ;
215+ try {
216+ const parsedUrl = new URL ( url ) ;
217+ if ( parsedUrl . username || parsedUrl . password ) {
218+ // Mask both username and password for security
219+ return `${ parsedUrl . protocol } //***:***@${ parsedUrl . host } ${ parsedUrl . pathname } ${ parsedUrl . search } ` ;
220+ }
221+ return url ;
222+ } catch {
223+ // If URL parsing fails, mask any potential credentials pattern
224+ return url . replace ( / \/ \/ [ ^ @ ] * @ / , '//***:***@' ) ;
225+ }
226+ }
227+
99228function isNullOrWhitespace ( input : any ) {
100229 if ( typeof input === 'undefined' || input == null ) {
101230 return true ;
0 commit comments