1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-03 17:08:35 -05:00

Merge commit from fork

* fix: drop auth headers, cookies on redirect to different origin

* refactor: destructure StringPrototypeEndsWith

---------

Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
This commit is contained in:
Espen Hovlandsdal 2024-11-25 10:07:00 -08:00 committed by Bartek Iwańczuk
parent 60760abd87
commit c380447de7
No known key found for this signature in database
GPG key ID: 0C6BCDDC3B3AD750
2 changed files with 97 additions and 1 deletions

View file

@ -31,6 +31,7 @@ const {
SafeArrayIterator,
SafePromisePrototypeFinally,
String,
StringPrototypeEndsWith,
StringPrototypeSlice,
StringPrototypeStartsWith,
StringPrototypeToLowerCase,
@ -66,6 +67,12 @@ const REQUEST_BODY_HEADER_NAMES = [
"content-type",
];
const REDIRECT_SENSITIVE_HEADER_NAMES = [
"authorization",
"proxy-authorization",
"cookie",
];
/**
* @param {number} rid
* @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) {
if (locationHeaders.length === 0) {
return response;
}
const currentURL = new URL(request.currentUrl());
const locationURL = new URL(
locationHeaders[0][1],
response.url() ?? undefined,
);
if (locationURL.hash === "") {
locationURL.hash = request.currentUrl().hash;
locationURL.hash = currentURL.hash;
}
if (locationURL.protocol !== "https:" && locationURL.protocol !== "http:") {
return networkError("Can not redirect to a non HTTP(s) url");
@ -297,6 +306,28 @@ function httpRedirectFetch(request, response, terminator) {
}
}
}
// Drop confidential headers when redirecting to a less secure protocol
// or to a different domain that is not a superdomain
if (
locationURL.protocol !== currentURL.protocol &&
locationURL.protocol !== "https:" ||
locationURL.host !== currentURL.host &&
!isSubdomain(locationURL.host, currentURL.host)
) {
for (let i = 0; i < request.headerList.length; i++) {
if (
ArrayPrototypeIncludes(
REDIRECT_SENSITIVE_HEADER_NAMES,
byteLowerCase(request.headerList[i][0]),
)
) {
ArrayPrototypeSplice(request.headerList, i, 1);
i--;
}
}
}
if (request.body !== null) {
const res = extractBody(request.body.source);
request.body = res.body;
@ -470,6 +501,19 @@ function abortFetch(request, responseObject, error) {
return error;
}
/**
* Checks if the given string is a subdomain of the given domain.
*
* @param {String} subdomain
* @param {String} domain
* @returns {Boolean}
*/
function isSubdomain(subdomain, domain) {
const dot = subdomain.length - domain.length - 1;
return dot > 0 && subdomain[dot] === "." &&
StringPrototypeEndsWith(subdomain, domain);
}
/**
* Handle the Response argument to the WebAssembly streaming APIs, after
* resolving if it was passed as a promise. This function should be registered

View file

@ -439,6 +439,58 @@ Deno.test(
},
);
Deno.test(
{
permissions: { net: true },
},
async function fetchWithAuthorizationHeaderRedirection() {
const response = await fetch("http://localhost:4546/echo_server", {
headers: { authorization: "Bearer foo" },
});
assertEquals(response.status, 200);
assertEquals(response.statusText, "OK");
assertEquals(response.url, "http://localhost:4545/echo_server");
assertEquals(response.headers.get("authorization"), null);
assertEquals(await response.text(), "");
},
);
Deno.test(
{
permissions: { net: true },
},
async function fetchWithCookieHeaderRedirection() {
const response = await fetch("http://localhost:4546/echo_server", {
headers: { Cookie: "sessionToken=verySecret" },
});
assertEquals(response.status, 200);
assertEquals(response.statusText, "OK");
assertEquals(response.url, "http://localhost:4545/echo_server");
assertEquals(response.headers.get("cookie"), null);
assertEquals(await response.text(), "");
},
);
Deno.test(
{
permissions: { net: true },
},
async function fetchWithProxyAuthorizationHeaderRedirection() {
const response = await fetch("http://localhost:4546/echo_server", {
headers: {
"proxy-authorization": "Basic ZXNwZW46a29rb3M=",
"accept": "application/json",
},
});
assertEquals(response.status, 200);
assertEquals(response.statusText, "OK");
assertEquals(response.url, "http://localhost:4545/echo_server");
assertEquals(response.headers.get("proxy-authorization"), null);
assertEquals(response.headers.get("accept"), "application/json");
assertEquals(await response.text(), "");
},
);
Deno.test(
{ permissions: { net: true } },
async function fetchInitStringBody() {