@@ -31,6 +31,7 @@ const {
31
31
SafeArrayIterator,
32
32
SafePromisePrototypeFinally,
33
33
String,
34
+ StringPrototypeEndsWith,
34
35
StringPrototypeSlice,
35
36
StringPrototypeStartsWith,
36
37
StringPrototypeToLowerCase,
@@ -66,6 +67,12 @@ const REQUEST_BODY_HEADER_NAMES = [
66
67
"content-type" ,
67
68
] ;
68
69
70
+ const REDIRECT_SENSITIVE_HEADER_NAMES = [
71
+ "authorization" ,
72
+ "proxy-authorization" ,
73
+ "cookie" ,
74
+ ] ;
75
+
69
76
/**
70
77
* @param {number } rid
71
78
* @returns {Promise<{ status: number, statusText: string, headers: [string, string][], url: string, responseRid: number, error: [string, string]? }> }
@@ -253,12 +260,14 @@ function httpRedirectFetch(request, response, terminator) {
253
260
if ( locationHeaders . length === 0 ) {
254
261
return response ;
255
262
}
263
+
264
+ const currentURL = new URL ( request . currentUrl ( ) ) ;
256
265
const locationURL = new URL (
257
266
locationHeaders [ 0 ] [ 1 ] ,
258
267
response . url ( ) ?? undefined ,
259
268
) ;
260
269
if ( locationURL . hash === "" ) {
261
- locationURL . hash = request . currentUrl ( ) . hash ;
270
+ locationURL . hash = currentURL . hash ;
262
271
}
263
272
if ( locationURL . protocol !== "https:" && locationURL . protocol !== "http:" ) {
264
273
return networkError ( "Can not redirect to a non HTTP(s) url" ) ;
@@ -297,6 +306,28 @@ function httpRedirectFetch(request, response, terminator) {
297
306
}
298
307
}
299
308
}
309
+
310
+ // Drop confidential headers when redirecting to a less secure protocol
311
+ // or to a different domain that is not a superdomain
312
+ if (
313
+ locationURL . protocol !== currentURL . protocol &&
314
+ locationURL . protocol !== "https:" ||
315
+ locationURL . host !== currentURL . host &&
316
+ ! isSubdomain ( locationURL . host , currentURL . host )
317
+ ) {
318
+ for ( let i = 0 ; i < request . headerList . length ; i ++ ) {
319
+ if (
320
+ ArrayPrototypeIncludes (
321
+ REDIRECT_SENSITIVE_HEADER_NAMES ,
322
+ byteLowerCase ( request . headerList [ i ] [ 0 ] ) ,
323
+ )
324
+ ) {
325
+ ArrayPrototypeSplice ( request . headerList , i , 1 ) ;
326
+ i -- ;
327
+ }
328
+ }
329
+ }
330
+
300
331
if ( request . body !== null ) {
301
332
const res = extractBody ( request . body . source ) ;
302
333
request . body = res . body ;
@@ -470,6 +501,19 @@ function abortFetch(request, responseObject, error) {
470
501
return error ;
471
502
}
472
503
504
+ /**
505
+ * Checks if the given string is a subdomain of the given domain.
506
+ *
507
+ * @param {String } subdomain
508
+ * @param {String } domain
509
+ * @returns {Boolean }
510
+ */
511
+ function isSubdomain ( subdomain , domain ) {
512
+ const dot = subdomain . length - domain . length - 1 ;
513
+ return dot > 0 && subdomain [ dot ] === "." &&
514
+ StringPrototypeEndsWith ( subdomain , domain ) ;
515
+ }
516
+
473
517
/**
474
518
* Handle the Response argument to the WebAssembly streaming APIs, after
475
519
* resolving if it was passed as a promise. This function should be registered
0 commit comments