This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch support/struts-6-x-x
in repository https://gitbox.apache.org/repos/asf/struts.git


The following commit(s) were added to refs/heads/support/struts-6-x-x by this 
push:
     new 370ac390f WW-5754 fix(core): accept cross-site HEAD navigations in the 
Fetch Metadata policy (#2123)
370ac390f is described below

commit 370ac390f9964c16b708a8ae4ca1df499c4e8c3e
Author: Lukasz Lenart <[email protected]>
AuthorDate: Sat Nov 7 18:30:34 2026 +0100

    WW-5754 fix(core): accept cross-site HEAD navigations in the Fetch Metadata 
policy (#2123)
    
    The documented policy lists HEAD alongside GET as an accepted method for
    cross-site navigations; WW-5753 accepted GET only.
    
    Cross-site POST navigations stay rejected: allowing them would re-open
    classic form-POST CSRF. Endpoints receiving SAML/OIDC form_post or
    payment-gateway returns opt out per action with the fetchMetadata
    interceptor's "disabled" parameter.
    
    Replaces testValidTopLevelNavigation, which sent no Sec-Fetch-Site header
    and so never reached the navigation branch.
    
    Co-authored-by: Claude Opus 5.5 <[email protected]>
---
 .../interceptor/StrutsResourceIsolationPolicy.java |  4 +++-
 .../interceptor/FetchMetadataInterceptorTest.java  | 25 ++++++++++------------
 2 files changed, 14 insertions(+), 15 deletions(-)

diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/StrutsResourceIsolationPolicy.java
 
b/core/src/main/java/org/apache/struts2/interceptor/StrutsResourceIsolationPolicy.java
index 146b493d2..6be10abbe 100644
--- 
a/core/src/main/java/org/apache/struts2/interceptor/StrutsResourceIsolationPolicy.java
+++ 
b/core/src/main/java/org/apache/struts2/interceptor/StrutsResourceIsolationPolicy.java
@@ -56,7 +56,9 @@ public final class StrutsResourceIsolationPolicy implements 
ResourceIsolationPol
         String mode = request.getHeader(SEC_FETCH_MODE_HEADER);
         String dest = request.getHeader(SEC_FETCH_DEST_HEADER);
 
-        boolean isSimpleTopLevelNavigation = 
MODE_NAVIGATE.equalsIgnoreCase(mode) && 
"GET".equalsIgnoreCase(request.getMethod());
+        String method = request.getMethod();
+        boolean isSimpleTopLevelNavigation = 
MODE_NAVIGATE.equalsIgnoreCase(mode)
+                && ("GET".equalsIgnoreCase(method) || 
"HEAD".equalsIgnoreCase(method));
         boolean isNotObjectOrEmbedRequest = !DEST_EMBED.equalsIgnoreCase(dest) 
&& !DEST_OBJECT.equalsIgnoreCase(dest);
 
         return isSimpleTopLevelNavigation && isNotObjectOrEmbedRequest;
diff --git 
a/core/src/test/java/org/apache/struts2/interceptor/FetchMetadataInterceptorTest.java
 
b/core/src/test/java/org/apache/struts2/interceptor/FetchMetadataInterceptorTest.java
index f60e98d28..9b52c94df 100644
--- 
a/core/src/test/java/org/apache/struts2/interceptor/FetchMetadataInterceptorTest.java
+++ 
b/core/src/test/java/org/apache/struts2/interceptor/FetchMetadataInterceptorTest.java
@@ -95,21 +95,18 @@ public class FetchMetadataInterceptorTest extends 
XWorkTestCase {
 
     }
 
-    public void testValidTopLevelNavigation() throws Exception {
-        request.addHeader(SEC_FETCH_MODE_HEADER, MODE_NAVIGATE);
-        request.addHeader(SEC_FETCH_DEST_HEADER, DEST_SCRIPT);
-        request.setMethod("GET");
-
-        assertNotEquals("Expected interceptor to accept this request", 
SC_FORBIDDEN, interceptor.intercept(mai));
-    }
-
-    public void testCrossSiteTopLevelGetNavigationAllowed() throws Exception {
-        request.addHeader(SEC_FETCH_SITE_HEADER, SITE_CROSS_SITE);
-        request.addHeader(SEC_FETCH_MODE_HEADER, MODE_NAVIGATE);
-        request.addHeader(SEC_FETCH_DEST_HEADER, DEST_DOCUMENT);
-        request.setMethod("GET");
+    public void testCrossSiteTopLevelGetAndHeadNavigationAllowed() throws 
Exception {
+        for (String method : Arrays.asList("GET", "HEAD")) {
+            request.removeHeader(SEC_FETCH_SITE_HEADER);
+            request.addHeader(SEC_FETCH_SITE_HEADER, SITE_CROSS_SITE);
+            request.removeHeader(SEC_FETCH_MODE_HEADER);
+            request.addHeader(SEC_FETCH_MODE_HEADER, MODE_NAVIGATE);
+            request.removeHeader(SEC_FETCH_DEST_HEADER);
+            request.addHeader(SEC_FETCH_DEST_HEADER, DEST_DOCUMENT);
+            request.setMethod(method);
 
-        assertNotEquals("Expected interceptor to accept this request", 
SC_FORBIDDEN, interceptor.intercept(mai));
+            assertNotEquals("Expected interceptor to accept cross-site " + 
method + " navigation", SC_FORBIDDEN, interceptor.intercept(mai));
+        }
     }
 
     public void testCrossSiteNavigationWithUnsafeMethodRejected() throws 
Exception {

Reply via email to