From 212dcc80adca44aea42dbf1327e4c5726b44e61c Mon Sep 17 00:00:00 2001 From: Vidya m Date: Thu, 29 Oct 2026 16:02:13 +0530 Subject: [PATCH] Drop the Authorization header for cross-origin redirects and scripts WebClient copied all additional headers of a request to the redirect target, and HtmlPage copied the headers of the page request to every external script request, regardless of the target origin. Remove the Authorization header in both cases if the origin changes, like XMLHttpRequest already does for its redirects. Add Vidya m to contributors in pom.xml. --- pom.xml | 3 ++ src/main/java/org/htmlunit/WebClient.java | 11 ++++++ src/main/java/org/htmlunit/html/HtmlPage.java | 5 +++ src/test/java/org/htmlunit/WebClientTest.java | 34 +++++++++++++++++ .../org/htmlunit/html/HtmlScriptTest.java | 37 +++++++++++++++++++ 5 files changed, 90 insertions(+) diff --git a/pom.xml b/pom.xml index 230865e0cd..cdb5f4ef7b 100644 --- a/pom.xml +++ b/pom.xml @@ -1430,6 +1430,9 @@ Vladimir Babin vovababin@gmail.com + + Vidya m + diff --git a/src/main/java/org/htmlunit/WebClient.java b/src/main/java/org/htmlunit/WebClient.java index 29a8be9e7e..87b8032fe8 100644 --- a/src/main/java/org/htmlunit/WebClient.java +++ b/src/main/java/org/htmlunit/WebClient.java @@ -153,6 +153,7 @@ * @author René Schwietzke * @author Sven Strickroth * @author Ashutosh Panda + * @author Vidya m */ @SuppressWarnings("PMD.TooManyFields") public class WebClient implements Serializable, AutoCloseable { @@ -1711,6 +1712,8 @@ && getOptions().isRedirectEnabled() + webResponse.getWebRequest().getUrl(), webResponse); } + final boolean crossOriginRedirect = !UrlUtils.isSameOrigin(url, newUrl); + if (status == HttpStatus.MOVED_PERMANENTLY_301 || status == HttpStatus.FOUND_302 || status == HttpStatus.SEE_OTHER_303) { @@ -1723,6 +1726,10 @@ && getOptions().isRedirectEnabled() for (final Map.Entry entry : webRequest.getAdditionalHeaders().entrySet()) { wrs.setAdditionalHeader(entry.getKey(), entry.getValue()); } + if (crossOriginRedirect) { + // credentials are not forwarded to another origin + wrs.removeAdditionalHeader("Authorization"); + } wrs.setFetchDestination(webRequest.getFetchDestination()); wrs.setFetchModeOverride(webRequest.getFetchModeOverride()); wrs.setRequestingUrl(webRequest.getRequestingUrl()); @@ -1751,6 +1758,10 @@ else if (status == HttpStatus.TEMPORARY_REDIRECT_307 for (final Map.Entry entry : webRequest.getAdditionalHeaders().entrySet()) { wrs.setAdditionalHeader(entry.getKey(), entry.getValue()); } + if (crossOriginRedirect) { + // credentials are not forwarded to another origin + wrs.removeAdditionalHeader("Authorization"); + } return loadWebResponseFromWebConnection(wrs, allowedRedirects - 1); } diff --git a/src/main/java/org/htmlunit/html/HtmlPage.java b/src/main/java/org/htmlunit/html/HtmlPage.java index eb10ac6724..5a0cbe7bf7 100644 --- a/src/main/java/org/htmlunit/html/HtmlPage.java +++ b/src/main/java/org/htmlunit/html/HtmlPage.java @@ -143,6 +143,7 @@ * @author Ronny Shapiro * @author Lai Quang Duong * @author Sven Strickroth + * @author Vidya m */ @SuppressWarnings("PMD.TooManyFields") public class HtmlPage extends SgmlPage { @@ -1105,6 +1106,10 @@ private Object loadJavaScriptFromUrl(final URL url, final Charset scriptCharset, final WebRequest request = new WebRequest(url); // copy all headers from the referring request request.setAdditionalHeaders(new HashMap<>(referringRequest.getAdditionalHeaders())); + if (!UrlUtils.isSameOrigin(referringRequest.getUrl(), url)) { + // credentials are not forwarded to another origin + request.removeAdditionalHeader("Authorization"); + } // at least overwrite this headers request.setAdditionalHeader(HttpHeader.ACCEPT, client.getBrowserVersion().getScriptAcceptHeader()); diff --git a/src/test/java/org/htmlunit/WebClientTest.java b/src/test/java/org/htmlunit/WebClientTest.java index 92ae189b2f..a34c483c75 100644 --- a/src/test/java/org/htmlunit/WebClientTest.java +++ b/src/test/java/org/htmlunit/WebClientTest.java @@ -74,6 +74,7 @@ * @author Ronald Brill * @author Carsten Steul * @author Joerg Werner + * @author Vidya m */ public class WebClientTest extends SimpleWebTestCase { @@ -526,6 +527,39 @@ private void redirection_AdditionalHeadersMaintained(final int statusCode) throw assertEquals("bar", conn.getLastAdditionalHeaders().get("foo")); } + /** + * Verifies that the "Authorization" header of the original {@link WebRequest} is only + * sent to the redirect location if the redirect does not leave the origin. + * @throws Exception if an error occurs + */ + @Test + public void redirection_AuthorizationHeaderDroppedForCrossOrigin() throws Exception { + for (final int statusCode : new int[] {301, 302, 303, 307, 308}) { + redirection_AuthorizationHeader(statusCode, URL_SECOND, "Bearer secret"); + redirection_AuthorizationHeader(statusCode, URL_THIRD, null); + } + } + + private void redirection_AuthorizationHeader(final int statusCode, final URL redirectUrl, + final String expectedAuthorization) throws Exception { + final WebClient client = getWebClient(); + final MockWebConnection conn = new MockWebConnection(); + client.setWebConnection(conn); + + final List headers = asList(new NameValuePair("Location", redirectUrl.toString())); + conn.setResponse(URL_FIRST, "", statusCode, "", MimeType.TEXT_HTML, headers); + conn.setResponse(redirectUrl, DOCTYPE_HTML + "abc"); + + final WebRequest request = new WebRequest(URL_FIRST); + request.setAdditionalHeader("Authorization", "Bearer secret"); + request.setAdditionalHeader("foo", "bar"); + client.getPage(request); + + assertEquals(redirectUrl, conn.getLastWebRequest().getUrl()); + assertEquals(expectedAuthorization, conn.getLastAdditionalHeaders().get("Authorization")); + assertEquals("bar", conn.getLastAdditionalHeaders().get("foo")); + } + /** * Basic logic for all the redirection tests. * diff --git a/src/test/java/org/htmlunit/html/HtmlScriptTest.java b/src/test/java/org/htmlunit/html/HtmlScriptTest.java index 729106bcaa..cf5bb989d2 100644 --- a/src/test/java/org/htmlunit/html/HtmlScriptTest.java +++ b/src/test/java/org/htmlunit/html/HtmlScriptTest.java @@ -25,6 +25,7 @@ import org.htmlunit.MockWebConnection; import org.htmlunit.SimpleWebTestCase; import org.htmlunit.WebClient; +import org.htmlunit.WebRequest; import org.htmlunit.junit.annotation.Alerts; import org.htmlunit.util.MimeType; import org.junit.jupiter.api.Test; @@ -37,6 +38,7 @@ * @author Ahmed Ashour * @author Ronald Brill * @author Ronny Shapiro + * @author Vidya m */ public class HtmlScriptTest extends SimpleWebTestCase { @@ -83,6 +85,41 @@ public void badExternalScriptReference() throws Exception { } } + /** + * Verifies that the "Authorization" header of the page request is only sent + * with the request for an external script if the script has the same origin. + * @throws Exception if an error occurs + */ + @Test + public void authorizationHeaderNotSentToCrossOriginScript() throws Exception { + externalScriptAuthorizationHeader(new URL(URL_FIRST, "script.js"), "Bearer secret"); + externalScriptAuthorizationHeader(new URL(URL_THIRD, "script.js"), null); + } + + private void externalScriptAuthorizationHeader(final URL scriptUrl, + final String expectedAuthorization) throws Exception { + final String html = DOCTYPE_HTML + + "foo\n" + + "\n" + + ""; + + final WebClient client = getWebClient(); + + final MockWebConnection webConnection = new MockWebConnection(); + webConnection.setResponse(URL_FIRST, html); + webConnection.setResponse(scriptUrl, "var i = 7;", MimeType.TEXT_JAVASCRIPT); + client.setWebConnection(webConnection); + + final WebRequest request = new WebRequest(URL_FIRST); + request.setAdditionalHeader("Authorization", "Bearer secret"); + request.setAdditionalHeader("foo", "bar"); + client.getPage(request); + + assertEquals(scriptUrl, webConnection.getLastWebRequest().getUrl()); + assertEquals(expectedAuthorization, webConnection.getLastAdditionalHeaders().get("Authorization")); + assertEquals("bar", webConnection.getLastAdditionalHeaders().get("foo")); + } + /** * @throws Exception if an error occurs */