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

jianbin pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/incubator-seata.git


The following commit(s) were added to refs/heads/2.x by this push:
     new 7b13e00f1c bugfix: fix IllegalArgumentException when GET request has 
request body (#8035)
7b13e00f1c is described below

commit 7b13e00f1c94f5d99b83447b689086c7bbd7174c
Author: Jiangke Wu <[email protected]>
AuthorDate: Thu Apr 2 14:33:05 2026 +0800

    bugfix: fix IllegalArgumentException when GET request has request body 
(#8035)
---
 changes/en-us/2.x.md                               |   1 +
 changes/zh-cn/2.x.md                               |   1 +
 .../namingserver/filter/ConsoleRemotingFilter.java | 158 ++++++++++++--
 .../filter/ConsoleRemotingFilterTest.java          | 243 +++++++++++++++++++++
 4 files changed, 387 insertions(+), 16 deletions(-)

diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index bfa311bcab..d2685150b1 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -37,6 +37,7 @@ Add changes here for all PR submitted to the 2.x branch.
 - [[#7956](https://github.com/apache/incubator-seata/pull/7956)] fix empty 
jacoco report on local when jdk above 17
 - [[#7965](https://github.com/apache/incubator-seata/pull/7965)] fix the issue 
where different element order in two lists causes failure to set the index as 
the primary key
 - [[#7992](https://github.com/apache/incubator-seata/pull/7992)] fix report 
branch transaction status without setting branch type
+- [[#8035](https://github.com/apache/incubator-seata/pull/8035)] fix 
IllegalArgumentException when GET request has request body
 
 ### optimize:
 
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index 4091e9fb42..c969ea2c87 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -38,6 +38,7 @@
 - [[#7956](https://github.com/apache/incubator-seata/pull/7956)] 
修复本地JDK17以上jacoco报告为空的问题
 - [[#7965](https://github.com/apache/incubator-seata/pull/7965)] 修复在 dm 和 
kingbase 中当没有将索引设置为主键索引时出现的问题
 - [[#7992](https://github.com/apache/incubator-seata/pull/7992)] 
修复报告分支事务状态时没有设置分支类型
+- [[#8035](https://github.com/apache/incubator-seata/pull/8035)] 
修复GET请求有请求体的时候出现 IllegalArgumentException
 
 
 ### optimize:
diff --git 
a/namingserver/src/main/java/org/apache/seata/namingserver/filter/ConsoleRemotingFilter.java
 
b/namingserver/src/main/java/org/apache/seata/namingserver/filter/ConsoleRemotingFilter.java
index 2a7357468d..b4c85a62fc 100644
--- 
a/namingserver/src/main/java/org/apache/seata/namingserver/filter/ConsoleRemotingFilter.java
+++ 
b/namingserver/src/main/java/org/apache/seata/namingserver/filter/ConsoleRemotingFilter.java
@@ -40,9 +40,10 @@ import org.springframework.web.client.RestTemplate;
 
 import java.io.IOException;
 import java.net.URI;
+import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.List;
-import java.util.Optional;
+import java.util.Locale;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.regex.Pattern;
 
@@ -57,13 +58,64 @@ public class ConsoleRemotingFilter implements Filter {
 
     private final Pattern urlPattern = Pattern.compile(CONSOLE_PATTERN);
 
-    private final Logger logger = 
LoggerFactory.getLogger(ConsoleRemotingFilter.class);
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(ConsoleRemotingFilter.class);
 
     public ConsoleRemotingFilter(NamingManager namingManager, RestTemplate 
restTemplate) {
         this.namingManager = namingManager;
         this.restTemplate = restTemplate;
     }
 
+    /**
+     * Check whether the proxied Content-Type is safe (will not be rendered as
+     * HTML / XML by the browser).  Only allow known-safe MIME types through
+     * (allowlist approach); everything else is replaced with
+     * {@code application/json}.
+     */
+    private static boolean isSafeContentType(String contentType) {
+        if (contentType == null) {
+            return false;
+        }
+        String lower = contentType.toLowerCase(Locale.ROOT);
+        // Extract the primary MIME type (ignore parameters such as charset)
+        int semicolonIdx = lower.indexOf(';');
+        String mimeType = (semicolonIdx >= 0 ? lower.substring(0, 
semicolonIdx) : lower).trim();
+        return "application/json".equals(mimeType)
+                || "text/plain".equals(mimeType)
+                || "application/octet-stream".equals(mimeType);
+    }
+
+    /**
+     * Validate that the given byte array looks like well-formed JSON
+     * (starts with '{' or '[' after trimming leading whitespace).
+     * This is a lightweight sanity check to prevent forwarding
+     * arbitrary HTML / script payloads disguised as JSON.
+     */
+    private static boolean looksLikeJson(byte[] body) {
+        if (body == null || body.length == 0) {
+            return true;
+        }
+        int i = 0;
+        // Skip optional UTF-8 BOM (0xEF, 0xBB, 0xBF)
+        if (body.length >= 3
+                && (body[0] & 0xFF) == 0xEF
+                && (body[1] & 0xFF) == 0xBB
+                && (body[2] & 0xFF) == 0xBF) {
+            i = 3;
+        }
+        // skip leading whitespace (including Unicode NBSP / BOM that survived 
as whitespace)
+        while (i < body.length && (body[i] == ' ' || body[i] == '\t'
+                || body[i] == '\r' || body[i] == '\n')) {
+            i++;
+        }
+        if (i >= body.length) {
+            return true;
+        }
+        byte first = body[i];
+        return first == '{' || first == '[' || first == '"'
+                || first == 't' || first == 'f' || first == 'n'
+                || (first >= '0' && first <= '9') || first == '-';
+    }
+
     @Override
     public void doFilter(ServletRequest servletRequest, ServletResponse 
servletResponse, FilterChain filterChain)
             throws IOException, ServletException {
@@ -99,42 +151,116 @@ public class ConsoleRemotingFilter implements Filter {
                                     + request.getRequestURI()
                                     + (request.getQueryString() != null ? "?" 
+ request.getQueryString() : "");
 
-                            // Copy headers from the original request
+                            // Copy headers from the original request, 
stripping hop-by-hop
+                            // headers (RFC 7230 §6.1) and Host (so the client 
library sets
+                            // the correct value for the upstream target).
                             HttpHeaders headers = new HttpHeaders();
                             if (node.getRole() == ClusterRole.LEADER) {
                                 headers.add(RAFT_GROUP_HEADER, node.getUnit());
                             }
                             Collections.list(request.getHeaderNames())
-                                    .forEach(headerName -> 
headers.add(headerName, request.getHeader(headerName)));
+                                    .forEach(headerName -> {
+                                        if 
(!HttpHeaders.HOST.equalsIgnoreCase(headerName)
+                                                && 
!HttpHeaders.CONNECTION.equalsIgnoreCase(headerName)
+                                                && 
!"Keep-Alive".equalsIgnoreCase(headerName)
+                                                && 
!HttpHeaders.PROXY_AUTHENTICATE.equalsIgnoreCase(headerName)
+                                                && 
!HttpHeaders.PROXY_AUTHORIZATION.equalsIgnoreCase(headerName)
+                                                && 
!HttpHeaders.TE.equalsIgnoreCase(headerName)
+                                                && 
!HttpHeaders.TRAILER.equalsIgnoreCase(headerName)
+                                                && 
!HttpHeaders.UPGRADE.equalsIgnoreCase(headerName)) {
+                                            headers.add(headerName, 
request.getHeader(headerName));
+                                        }
+                                    });
 
                             // Create the HttpEntity with headers and body
-                            HttpEntity<byte[]> httpEntity = new 
HttpEntity<>(request.getCachedBody(), headers);
                             HttpMethod httpMethod;
                             try {
                                 httpMethod = 
HttpMethod.valueOf(request.getMethod());
                             } catch (IllegalArgumentException ex) {
-                                logger.error("Unsupported HTTP method: {}", 
request.getMethod(), ex);
+                                LOGGER.error("Unsupported HTTP method: {}", 
request.getMethod(), ex);
                                 
response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
                                 return;
                             }
+
+                            // GET/HEAD methods should not have a body; other 
methods may include a body as needed.
+                            HttpEntity<byte[]> httpEntity;
+                            if (HttpMethod.GET.equals(httpMethod) || 
HttpMethod.HEAD.equals(httpMethod)) {
+                                headers.remove(HttpHeaders.CONTENT_LENGTH);
+                                headers.remove(HttpHeaders.TRANSFER_ENCODING);
+                                // headers-only
+                                httpEntity = new HttpEntity<>(headers);
+                            } else {
+                                byte[] body = request.getCachedBody();
+                                if (body == null || body.length == 0) {
+                                    headers.remove(HttpHeaders.CONTENT_LENGTH);
+                                    
headers.remove(HttpHeaders.TRANSFER_ENCODING);
+                                    // headers-only for empty body
+                                    httpEntity = new HttpEntity<>(headers);
+                                } else {
+                                    // Remove potentially stale 
length/transfer headers and let the client recompute them
+                                    headers.remove(HttpHeaders.CONTENT_LENGTH);
+                                    
headers.remove(HttpHeaders.TRANSFER_ENCODING);
+                                    httpEntity = new HttpEntity<>(body, 
headers);
+                                }
+                            }
+
                             try {
-                                ResponseEntity<byte[]> responseEntity = 
restTemplate.exchange(
-                                        URI.create(targetUrl), httpMethod, 
httpEntity, byte[].class);
+                                ResponseEntity<byte[]> responseEntity = 
restTemplate.exchange(URI.create(targetUrl), httpMethod, httpEntity, 
byte[].class);
+                                //Copy headers from proxied response, skipping 
hop-by-hop and headers we manage ourselves to mitigate
+                                // security risks from Content-Type 
manipulation
                                 responseEntity.getHeaders().forEach((key, 
value) -> {
-                                    value.forEach(v -> response.addHeader(key, 
v));
+                                    if 
(!HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(key)
+                                            && 
!HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(key)
+                                            && 
!HttpHeaders.TRANSFER_ENCODING.equalsIgnoreCase(key)
+                                            && 
!"X-Content-Type-Options".equalsIgnoreCase(key)
+                                            && 
!HttpHeaders.CONNECTION.equalsIgnoreCase(key)
+                                            && 
!"Keep-Alive".equalsIgnoreCase(key)
+                                            && 
!HttpHeaders.PROXY_AUTHENTICATE.equalsIgnoreCase(key)
+                                            && 
!HttpHeaders.PROXY_AUTHORIZATION.equalsIgnoreCase(key)
+                                            && 
!HttpHeaders.TE.equalsIgnoreCase(key)
+                                            && 
!HttpHeaders.TRAILER.equalsIgnoreCase(key)
+                                            && 
!HttpHeaders.UPGRADE.equalsIgnoreCase(key)) {
+                                        value.forEach(v -> 
response.addHeader(key, v));
+                                    }
                                 });
-                                response.setStatus(
-                                        
responseEntity.getStatusCode().value());
-                                
Optional.ofNullable(responseEntity.getBody()).ifPresent(body -> {
+                                // Force a safe Content-Type: reject HTML/XML 
types that could
+                                // execute scripts; fall back to 
application/json
+                                String proxiedContentType = 
responseEntity.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
+                                String safeContentType;
+                                if (isSafeContentType(proxiedContentType)) {
+                                    safeContentType = proxiedContentType;
+                                } else {
+                                    safeContentType = 
"application/json;charset=UTF-8";
+                                }
+                                response.setContentType(safeContentType);
+                                response.setHeader("X-Content-Type-Options", 
"nosniff");
+                                
response.setStatus(responseEntity.getStatusCode().value());
+                                byte[] responseBody = responseEntity.getBody();
+                                // HEAD responses must not include a message 
body (RFC 7231 §4.3.2)
+                                if (!HttpMethod.HEAD.equals(httpMethod)
+                                        && responseBody != null && 
responseBody.length > 0) {
+                                    // For JSON content type, validate that 
the body actually looks
+                                    // like JSON to prevent XSS via crafted 
upstream responses
+                                    if 
(safeContentType.toLowerCase(Locale.ROOT).contains("application/json")
+                                            && !looksLikeJson(responseBody)) {
+                                        LOGGER.warn("Upstream returned 
non-JSON body for Content-Type {}, replacing with error response", 
safeContentType);
+                                        
response.setStatus(HttpServletResponse.SC_BAD_GATEWAY);
+                                        
response.setContentType("application/json;charset=UTF-8");
+                                        responseBody = "{\"error\":\"Upstream 
returned invalid response body\"}"
+                                                
.getBytes(StandardCharsets.UTF_8);
+                                    }
                                     try (ServletOutputStream outputStream = 
response.getOutputStream()) {
-                                        outputStream.write(body);
+                                        outputStream.write(responseBody);
                                         outputStream.flush();
                                     } catch (IOException e) {
-                                        logger.error(e.getMessage(), e);
+                                        // Client likely disconnected (broken 
pipe); log at debug
+                                        // level and do NOT attempt sendError 
– the response may
+                                        // already be committed.
+                                        LOGGER.debug("Failed to write proxy 
response body (client disconnect?): {}", e.getMessage(), e);
                                     }
-                                });
+                                }
                             } catch (Exception ex) {
-                                logger.error(ex.getMessage(), ex);
+                                LOGGER.error(ex.getMessage(), ex);
                                 
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                             }
                             return;
diff --git 
a/namingserver/src/test/java/org/apache/seata/namingserver/filter/ConsoleRemotingFilterTest.java
 
b/namingserver/src/test/java/org/apache/seata/namingserver/filter/ConsoleRemotingFilterTest.java
new file mode 100644
index 0000000000..0efa2da41c
--- /dev/null
+++ 
b/namingserver/src/test/java/org/apache/seata/namingserver/filter/ConsoleRemotingFilterTest.java
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seata.namingserver.filter;
+
+import jakarta.servlet.FilterChain;
+import org.apache.seata.common.metadata.Node;
+import org.apache.seata.common.metadata.namingserver.NamingServerNode;
+import org.apache.seata.namingserver.manager.NamingManager;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.web.client.RestTemplate;
+
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Unit tests for {@link ConsoleRemotingFilter}.
+ * <p>
+ * Covers the GET/HEAD body-stripping regression (OkHttp 
IllegalArgumentException)
+ * and other proxy-forwarding behavior.
+ */
+class ConsoleRemotingFilterTest {
+
+    private NamingManager namingManager;
+    private RestTemplate restTemplate;
+    private ConsoleRemotingFilter filter;
+    private FilterChain filterChain;
+
+    private static final String NAMESPACE = "public";
+    private static final String CLUSTER = "default";
+    private static final String TARGET_HOST = "127.0.0.1";
+    private static final int TARGET_PORT = 7091;
+
+    @BeforeEach
+    void setUp() {
+        namingManager = mock(NamingManager.class);
+        restTemplate = mock(RestTemplate.class);
+        filterChain = mock(FilterChain.class);
+        filter = new ConsoleRemotingFilter(namingManager, restTemplate);
+
+        // Set up a NamingServerNode with a control endpoint
+        NamingServerNode node = new NamingServerNode();
+        node.setControl(new Node.Endpoint(TARGET_HOST, TARGET_PORT, "http"));
+
+        when(namingManager.getInstances(NAMESPACE, CLUSTER))
+                .thenReturn(Collections.singletonList(node));
+    }
+
+    /**
+     * Regression test: a GET request with a non-empty body should NOT forward
+     * the body to the upstream server (to avoid OkHttp's 
IllegalArgumentException).
+     * The body, Content-Length, and Transfer-Encoding headers must be 
stripped.
+     */
+    @Test
+    void getRequestWithBodyShouldStripBody() throws Exception {
+        // Prepare a GET request with a body (some clients/frameworks may 
attach one)
+        MockHttpServletRequest request = createConsoleRequest("GET");
+        
request.setContent("{\"key\":\"value\"}".getBytes(StandardCharsets.UTF_8));
+        request.addHeader(HttpHeaders.CONTENT_LENGTH, "15");
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+
+        // Stub RestTemplate to return a successful JSON response
+        HttpHeaders responseHeaders = new HttpHeaders();
+        responseHeaders.set(HttpHeaders.CONTENT_TYPE, 
"application/json;charset=UTF-8");
+        ResponseEntity<byte[]> upstreamResponse = new ResponseEntity<>(
+                "{\"result\":\"ok\"}".getBytes(StandardCharsets.UTF_8),
+                responseHeaders,
+                HttpStatus.OK);
+
+        when(restTemplate.exchange(any(URI.class), eq(HttpMethod.GET), 
any(HttpEntity.class), eq(byte[].class)))
+                .thenReturn(upstreamResponse);
+
+        filter.doFilter(request, response, filterChain);
+
+        // Capture the HttpEntity sent to RestTemplate
+        @SuppressWarnings("unchecked")
+        ArgumentCaptor<HttpEntity<byte[]>> entityCaptor = 
ArgumentCaptor.forClass(HttpEntity.class);
+        verify(restTemplate).exchange(any(URI.class), eq(HttpMethod.GET), 
entityCaptor.capture(), eq(byte[].class));
+
+        HttpEntity<byte[]> capturedEntity = entityCaptor.getValue();
+        // Body must be null (stripped for GET)
+        assertNull(capturedEntity.getBody(), "GET request body should be 
stripped (null)");
+        // Content-Length and Transfer-Encoding headers must not be forwarded
+        assertNull(capturedEntity.getHeaders().get(HttpHeaders.CONTENT_LENGTH),
+                "Content-Length header should be removed for GET");
+        
assertNull(capturedEntity.getHeaders().get(HttpHeaders.TRANSFER_ENCODING),
+                "Transfer-Encoding header should be removed for GET");
+
+        // Verify filterChain was NOT invoked (proxied)
+        verify(filterChain, never()).doFilter(any(), any());
+        assertEquals(200, response.getStatus());
+    }
+
+    /**
+     * HEAD request should also strip the body, same as GET.
+     */
+    @Test
+    void headRequestShouldStripBody() throws Exception {
+        MockHttpServletRequest request = createConsoleRequest("HEAD");
+        request.setContent("some body".getBytes(StandardCharsets.UTF_8));
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+
+        HttpHeaders responseHeaders = new HttpHeaders();
+        responseHeaders.set(HttpHeaders.CONTENT_TYPE, "application/json");
+        ResponseEntity<byte[]> upstreamResponse = new ResponseEntity<>(
+                null, responseHeaders, HttpStatus.OK);
+
+        when(restTemplate.exchange(any(URI.class), eq(HttpMethod.HEAD), 
any(HttpEntity.class), eq(byte[].class)))
+                .thenReturn(upstreamResponse);
+
+        filter.doFilter(request, response, filterChain);
+
+        @SuppressWarnings("unchecked")
+        ArgumentCaptor<HttpEntity<byte[]>> entityCaptor = 
ArgumentCaptor.forClass(HttpEntity.class);
+        verify(restTemplate).exchange(any(URI.class), eq(HttpMethod.HEAD), 
entityCaptor.capture(), eq(byte[].class));
+
+        assertNull(entityCaptor.getValue().getBody(), "HEAD request body 
should be stripped (null)");
+        verify(filterChain, never()).doFilter(any(), any());
+    }
+
+    /**
+     * POST request should forward the body as-is.
+     */
+    @Test
+    void postRequestShouldForwardBody() throws Exception {
+        byte[] bodyBytes = 
"{\"data\":\"test\"}".getBytes(StandardCharsets.UTF_8);
+        MockHttpServletRequest request = createConsoleRequest("POST");
+        request.setContent(bodyBytes);
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+
+        HttpHeaders responseHeaders = new HttpHeaders();
+        responseHeaders.set(HttpHeaders.CONTENT_TYPE, 
"application/json;charset=UTF-8");
+        ResponseEntity<byte[]> upstreamResponse = new ResponseEntity<>(
+                "{\"result\":\"created\"}".getBytes(StandardCharsets.UTF_8),
+                responseHeaders,
+                HttpStatus.OK);
+
+        when(restTemplate.exchange(any(URI.class), eq(HttpMethod.POST), 
any(HttpEntity.class), eq(byte[].class)))
+                .thenReturn(upstreamResponse);
+
+        filter.doFilter(request, response, filterChain);
+
+        @SuppressWarnings("unchecked")
+        ArgumentCaptor<HttpEntity<byte[]>> entityCaptor = 
ArgumentCaptor.forClass(HttpEntity.class);
+        verify(restTemplate).exchange(any(URI.class), eq(HttpMethod.POST), 
entityCaptor.capture(), eq(byte[].class));
+
+        byte[] capturedBody = entityCaptor.getValue().getBody();
+        assertNotNull(capturedBody, "POST body should not be null");
+        assertEquals(new String(bodyBytes, StandardCharsets.UTF_8),
+                new String(capturedBody, StandardCharsets.UTF_8),
+                "POST request body should be forwarded as-is");
+    }
+
+    /**
+     * Non-matching URL should pass through the filter chain without proxying.
+     */
+    @Test
+    void nonConsoleUrlShouldPassThrough() throws Exception {
+        MockHttpServletRequest request = new MockHttpServletRequest("GET", 
"/api/v1/other/endpoint");
+        MockHttpServletResponse response = new MockHttpServletResponse();
+
+        filter.doFilter(request, response, filterChain);
+
+        verify(filterChain).doFilter(any(), any());
+        verify(restTemplate, never()).exchange(any(URI.class), any(), 
any(HttpEntity.class), eq(byte[].class));
+    }
+
+    /**
+     * Upstream returning an HTML body with application/json Content-Type 
should
+     * be replaced with a 502 error response.
+     */
+    @Test
+    void nonJsonBodyWithJsonContentTypeShouldReturn502() throws Exception {
+        MockHttpServletRequest request = createConsoleRequest("GET");
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+
+        HttpHeaders responseHeaders = new HttpHeaders();
+        responseHeaders.set(HttpHeaders.CONTENT_TYPE, "application/json");
+        // Upstream sends HTML disguised as JSON
+        byte[] htmlBody = 
"<html><script>alert('xss')</script></html>".getBytes(StandardCharsets.UTF_8);
+        ResponseEntity<byte[]> upstreamResponse = new ResponseEntity<>(
+                htmlBody, responseHeaders, HttpStatus.OK);
+
+        when(restTemplate.exchange(any(URI.class), eq(HttpMethod.GET), 
any(HttpEntity.class), eq(byte[].class)))
+                .thenReturn(upstreamResponse);
+
+        filter.doFilter(request, response, filterChain);
+
+        assertEquals(502, response.getStatus(),
+                "Should return 502 when upstream body is not valid JSON");
+        String body = response.getContentAsString();
+        assertEquals("{\"error\":\"Upstream returned invalid response 
body\"}", body);
+    }
+
+    /**
+     * Helper: create a MockHttpServletRequest that matches the console URL 
pattern
+     * and includes the required namespace/cluster headers.
+     */
+    private MockHttpServletRequest createConsoleRequest(String method) {
+        MockHttpServletRequest request = new MockHttpServletRequest(method, 
"/api/v1/console/globalSession/query");
+        request.addHeader("x-seata-namespace", NAMESPACE);
+        request.addHeader("x-seata-cluster", CLUSTER);
+        return request;
+    }
+}
+


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to